summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PACKAGE_MANAGER_OWNERS3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java141
-rw-r--r--api/Android.bp35
-rw-r--r--cmds/telecom/Android.bp5
-rw-r--r--cmds/telecom/src/com/android/commands/telecom/Telecom.java481
-rw-r--r--core/api/current.txt42
-rw-r--r--core/api/system-current.txt82
-rw-r--r--core/api/system-lint-baseline.txt6
-rw-r--r--core/java/android/app/AutomaticZenRule.java18
-rw-r--r--core/java/android/app/Notification.java42
-rw-r--r--core/java/android/app/NotificationManager.java66
-rw-r--r--core/java/android/app/assist/AssistStructure.java67
-rw-r--r--core/java/android/app/ondeviceintelligence/Feature.java23
-rw-r--r--core/java/android/app/ondeviceintelligence/FeatureDetails.java30
-rw-r--r--core/java/android/app/ondeviceintelligence/FilePart.java137
-rw-r--r--core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl6
-rw-r--r--core/java/android/app/ondeviceintelligence/IResponseCallback.aidl2
-rw-r--r--core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl2
-rw-r--r--core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl13
-rw-r--r--core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl14
-rw-r--r--core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java170
-rw-r--r--core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java54
-rw-r--r--core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java (renamed from core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java)14
-rw-r--r--core/java/android/app/ondeviceintelligence/TokenInfo.aidl22
-rw-r--r--core/java/android/app/ondeviceintelligence/TokenInfo.java94
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java6
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationManager.java23
-rw-r--r--core/java/android/hardware/SensorPrivacyManager.java2
-rw-r--r--core/java/android/hardware/biometrics/flags.aconfig7
-rw-r--r--core/java/android/hardware/face/FaceManager.java240
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl9
-rw-r--r--core/java/android/os/vibrator/VibrationConfig.java12
-rw-r--r--core/java/android/permission/PermissionManager.java6
-rw-r--r--core/java/android/provider/Settings.java19
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java20
-rw-r--r--core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl10
-rw-r--r--core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl (renamed from core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl)14
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java70
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java (renamed from core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java)172
-rw-r--r--core/java/android/view/MotionEvent.java6
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/view/ViewStructure.java3
-rw-r--r--core/java/android/widget/RemoteViews.java13
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl3
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java13
-rw-r--r--core/jni/LayoutlibLoader.cpp2
-rw-r--r--core/jni/android_view_MotionEvent.cpp16
-rw-r--r--core/res/AndroidManifest.xml11
-rw-r--r--core/res/res/values-watch/themes_device_defaults.xml2
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java2
-rw-r--r--core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java45
-rw-r--r--core/tests/coretests/src/android/hardware/face/FaceManagerTest.java23
-rw-r--r--core/tests/coretests/src/android/view/ViewVelocityTest.java6
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--graphics/java/android/graphics/Canvas.java6
-rw-r--r--graphics/java/android/graphics/Matrix44.java14
-rw-r--r--libs/WindowManager/Shell/res/drawable/circular_progress.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml8
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml25
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml9
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml9
-rw-r--r--libs/WindowManager/Shell/res/layout/maximize_menu_button.xml10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java17
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp51
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp80
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml10
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml5
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml6
-rw-r--r--libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict11
-rw-r--r--location/java/android/location/provider/ForwardGeocodeRequest.java6
-rw-r--r--location/java/android/location/provider/ReverseGeocodeRequest.java6
-rw-r--r--native/android/input.cpp4
-rw-r--r--nfc/Android.bp1
-rw-r--r--nfc/api/current.txt8
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java33
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt43
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt51
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java97
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java120
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java4
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java9
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java5
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java18
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java95
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java3
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt125
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt270
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt128
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt548
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt113
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt327
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt147
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt)66
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt275
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java268
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java453
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt286
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt312
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt224
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt183
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java121
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt101
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt64
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt4
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java232
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java21
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java149
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java34
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java8
-rw-r--r--services/core/java/com/android/server/DropBoxManagerService.java2
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java542
-rw-r--r--services/core/java/com/android/server/am/BroadcastDispatcher.java1266
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java1983
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java62
-rw-r--r--services/core/java/com/android/server/am/ConnectionRecord.java11
-rw-r--r--services/core/java/com/android/server/am/ContentProviderConnection.java12
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java21
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java649
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java4
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java32
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java35
-rw-r--r--services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java64
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java12
-rw-r--r--services/core/java/com/android/server/connectivity/TEST_MAPPING18
-rw-r--r--services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java19
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java10
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java3
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java16
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java12
-rw-r--r--services/core/java/com/android/server/net/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java51
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java162
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java61
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java (renamed from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java)16
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java15
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java45
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java40
-rw-r--r--services/core/java/com/android/server/power/ThermalManagerService.java35
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java1
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java23
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java31
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java26
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java28
-rw-r--r--services/core/java/com/android/server/wm/Task.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java43
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java47
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java3
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt2
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java39
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java26
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java162
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java64
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java237
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt61
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java10
-rw-r--r--services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java10
-rw-r--r--services/tests/uiservicestests/Android.bp2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java57
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java125
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java47
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java4
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbHostManager.java8
-rw-r--r--telephony/java/android/telephony/ims/ImsService.java7
-rw-r--r--tests/FlickerTests/Android.bp13
-rw-r--r--tests/FlickerTests/libs/window-extensions-release.aarbin21364 -> 0 bytes
-rw-r--r--tools/streaming_proto/Android.bp56
-rw-r--r--tools/streaming_proto/java/java_proto_stream_code_generator.cpp339
-rw-r--r--tools/streaming_proto/java/java_proto_stream_code_generator.h29
-rw-r--r--tools/streaming_proto/java/main.cpp278
-rw-r--r--tools/streaming_proto/test/integration/imported.proto (renamed from tools/streaming_proto/test/imported.proto)0
-rw-r--r--tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java23
-rw-r--r--tools/streaming_proto/test/integration/test.proto (renamed from tools/streaming_proto/test/test.proto)2
-rw-r--r--tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java7
-rw-r--r--tools/streaming_proto/test/unit/streaming_proto_java.cpp191
361 files changed, 11498 insertions, 8383 deletions
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842ba58ce..45719a7ced09 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
alexbuy@google.com
patb@google.com
schfan@google.com \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e96d07f44b34..ee9400fb8408 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -46,8 +46,11 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -68,6 +71,8 @@ import com.android.server.utils.AlarmQueue;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -1620,9 +1625,21 @@ public final class FlexibilityController extends StateController {
private final Object mSatLock = new Object();
private DeviceIdleInternal mDeviceIdleInternal;
+ private TelephonyManager mTelephonyManager;
+
+ private final boolean mHasFeatureTelephonySubscription;
/** Set of all apps that have been deemed special, keyed by user ID. */
private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
+ /**
+ * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged
+ * for.
+ */
+ @GuardedBy("mSatLock")
+ private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>();
+ @GuardedBy("mSatLock")
+ private final SparseArray<LogicalIndexCarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
@GuardedBy("mSatLock")
private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
@@ -1630,6 +1647,10 @@ public final class FlexibilityController extends StateController {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ updateCarrierPrivilegedCallbackRegistration();
+ break;
+
case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
break;
@@ -1637,6 +1658,11 @@ public final class FlexibilityController extends StateController {
}
};
+ SpecialAppTracker() {
+ mHasFeatureTelephonySubscription = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+ }
+
public boolean isSpecialApp(final int userId, @NonNull String packageName) {
synchronized (mSatLock) {
if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
@@ -1654,6 +1680,12 @@ public final class FlexibilityController extends StateController {
if (mPowerAllowlistedApps.contains(packageName)) {
return true;
}
+ for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) {
+ if (mCarrierPrivilegedApps.contains(
+ mCarrierPrivilegedApps.keyAt(l), packageName)) {
+ return true;
+ }
+ }
}
return false;
}
@@ -1669,9 +1701,12 @@ public final class FlexibilityController extends StateController {
private void onSystemServicesReady() {
mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
synchronized (mLock) {
if (mFlexibilityEnabled) {
+ mHandler.post(
+ SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration);
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
}
}
@@ -1686,6 +1721,13 @@ public final class FlexibilityController extends StateController {
private void startTracking() {
IntentFilter filter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+
+ if (mHasFeatureTelephonySubscription) {
+ filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+
+ updateCarrierPrivilegedCallbackRegistration();
+ }
+
mContext.registerReceiver(mBroadcastReceiver, filter);
updatePowerAllowlistCache();
@@ -1695,9 +1737,61 @@ public final class FlexibilityController extends StateController {
mContext.unregisterReceiver(mBroadcastReceiver);
synchronized (mSatLock) {
+ mCarrierPrivilegedApps.clear();
mPowerAllowlistedApps.clear();
mSpecialApps.clear();
+
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(
+ mCarrierPrivilegedCallbacks.valueAt(i));
+ }
+ mCarrierPrivilegedCallbacks.clear();
+ }
+ }
+
+ private void updateCarrierPrivilegedCallbackRegistration() {
+ if (mTelephonyManager == null) {
+ return;
+ }
+ if (!mHasFeatureTelephonySubscription) {
+ return;
+ }
+
+ Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final IntArray callbacksToRemove = new IntArray();
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i));
+ }
+ for (UiccSlotMapping mapping : simSlotMapping) {
+ final int logicalIndex = mapping.getLogicalSlotIndex();
+ if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
+ // Callback already exists. No need to create a new one or remove it.
+ callbacksToRemove.remove(logicalIndex);
+ continue;
+ }
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ new LogicalIndexCarrierPrivilegesCallback(logicalIndex);
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+ // Upon registration, the callbacks will be called with the current list of
+ // apps, so there's no need to query the app list synchronously.
+ mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex,
+ AppSchedulingModuleThread.getExecutor(), callback);
+ }
+
+ for (int i = callbacksToRemove.size() - 1; i >= 0; --i) {
+ final int logicalIndex = callbacksToRemove.get(i);
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
+ mCarrierPrivilegedCallbacks.remove(logicalIndex);
+ changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex));
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
}
+
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
}
/**
@@ -1762,18 +1856,65 @@ public final class FlexibilityController extends StateController {
updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
}
+ class LogicalIndexCarrierPrivilegesCallback implements
+ TelephonyManager.CarrierPrivilegesCallback {
+ public final int logicalIndex;
+
+ LogicalIndexCarrierPrivilegesCallback(int logicalIndex) {
+ this.logicalIndex = logicalIndex;
+ }
+
+ @Override
+ public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
+ @NonNull Set<Integer> privilegedUids) {
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final ArraySet<String> oldPrivilegedSet =
+ mCarrierPrivilegedApps.get(logicalIndex);
+ if (oldPrivilegedSet != null) {
+ changedPkgs.addAll(oldPrivilegedSet);
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
+ for (String pkgName : privilegedPackageNames) {
+ mCarrierPrivilegedApps.add(logicalIndex, pkgName);
+ if (!changedPkgs.remove(pkgName)) {
+ // The package wasn't in the previous set of privileged apps. Add it
+ // since its state has changed.
+ changedPkgs.add(pkgName);
+ }
+ }
+ }
+
+ // The carrier privileged list doesn't provide a simple userId correlation,
+ // so for now, use USER_ALL for these packages.
+ // TODO(141645789): use the UID list to narrow down to specific userIds
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+ }
+ }
+
public void dump(@NonNull IndentingPrintWriter pw) {
pw.println("Special apps:");
pw.increaseIndent();
synchronized (mSatLock) {
for (int u = 0; u < mSpecialApps.size(); ++u) {
+ pw.print("User ");
pw.print(mSpecialApps.keyAt(u));
pw.print(": ");
pw.println(mSpecialApps.valuesAt(u));
}
pw.println();
+ pw.println("Carrier privileged packages:");
+ pw.increaseIndent();
+ for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) {
+ pw.print(mCarrierPrivilegedApps.keyAt(i));
+ pw.print(": ");
+ pw.println(mCarrierPrivilegedApps.valuesAt(i));
+ }
+ pw.decreaseIndent();
+
+ pw.println();
pw.print("Power allowlisted packages: ");
pw.println(mPowerAllowlistedApps);
}
diff --git a/api/Android.bp b/api/Android.bp
index 8e063667826c..093ee4beab50 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@ packages_to_document = [
"org.xmlpull",
]
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+]
+
+java_defaults {
+ name: "android-non-updatable-stubs-libs-defaults",
+ libs: android_non_updatable_stubs_libs,
+}
+
// Defaults for all stubs that include the non-updatable framework. These defaults do not include
// module symbols, so will not compile correctly on their own. Users must add module APIs to the
// classpath (or sources) somehow.
@@ -329,18 +351,7 @@ stubs_defaults {
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
// dependencies gets bigger.
- libs: [
- "android.hardware.cas-V1.2-java",
- "android.hardware.health-V1.0-java-constants",
- "android.hardware.thermal-V1.0-java-constants",
- "android.hardware.thermal-V2.0-java",
- "android.hardware.tv.input-V1.0-java-constants",
- "android.hardware.usb-V1.0-java-constants",
- "android.hardware.usb-V1.1-java-constants",
- "android.hardware.usb.gadget-V1.0-java",
- "android.hardware.vibrator-V1.3-java",
- "framework-protos",
- ],
+ libs: android_non_updatable_stubs_libs,
flags: [
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp
index be027105ae98..494d2ae37d53 100644
--- a/cmds/telecom/Android.bp
+++ b/cmds/telecom/Android.bp
@@ -21,5 +21,8 @@ license {
java_binary {
name: "telecom",
wrapper: "telecom.sh",
- srcs: ["**/*.java"],
+ srcs: [
+ ":telecom-shell-commands-src",
+ "**/*.java",
+ ],
}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 1488e14cfb8f..50af5a7a29b7 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -17,30 +17,22 @@
package com.android.commands.telecom;
import android.app.ActivityThread;
-import android.content.ComponentName;
import android.content.Context;
-import android.net.Uri;
-import android.os.IUserManager;
import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.sysprop.TelephonyProperties;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.TelecomShellCommand;
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.io.FileDescriptor;
-public final class Telecom extends BaseCommand {
+/**
+ * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the
+ * shell command using {@code adb shell cmd telecom...}. This is only here for backwards
+ * compatibility reasons.
+ */
+@Deprecated
+public final class Telecom {
/**
* Command-line entry point.
@@ -52,458 +44,11 @@ public final class Telecom extends BaseCommand {
// TODO: Do it in zygote and RuntimeInit. b/148897549
ActivityThread.initializeMainlineModules();
- (new Telecom()).run(args);
- }
- private static final String CALLING_PACKAGE = Telecom.class.getPackageName();
- private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
- private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
- private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
- private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
- "set-user-selected-outgoing-phone-account";
- private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
- private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
- private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
- private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
- "add-or-remove-call-companion-app";
- private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
- "set-phone-acct-suggestion-component";
- private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
- private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
- private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
- private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
- private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
- private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
- private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
- "cleanup-orphan-phone-accounts";
- private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
- private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
- "is-non-ui-in-call-service-bound";
-
- /**
- * Change the system dialer package name if a package name was specified,
- * Example: adb shell telecom set-system-dialer <PACKAGE>
- *
- * Restore it to the default if if argument is "default" or no argument is passed.
- * Example: adb shell telecom set-system-dialer default
- */
- private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
- private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
- private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
- private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
- private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
- private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
- private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
- "set-test-emergency-phone-account-package-filter";
- /**
- * Command used to emit a distinct "mark" in the logs.
- */
- private static final String COMMAND_LOG_MARK = "log-mark";
-
- private ComponentName mComponent;
- private String mAccountId;
- private ITelecomService mTelecomService;
- private TelephonyManager mTelephonyManager;
- private IUserManager mUserManager;
-
- @Override
- public void onShowUsage(PrintStream out) {
- out.println("usage: telecom [subcommand] [options]\n"
- + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
- + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
- + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
- + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
- + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
- + "<USER_SN>\n"
- + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
- + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
- + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
- + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
- + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
- + " <LABEL> <ADDRESS>\n"
- + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
- + "usage: telecom set-default-dialer <PACKAGE>\n"
- + "usage: telecom get-default-dialer\n"
- + "usage: telecom get-system-dialer\n"
- + "usage: telecom wait-on-handlers\n"
- + "usage: telecom set-sim-count <COUNT>\n"
- + "usage: telecom get-sim-config\n"
- + "usage: telecom get-max-phones\n"
- + "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
- + " provider after a call to emergency services.\n"
- + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
- + " gotten wedged in Telecom.\n"
- + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
- + " no longer have a valid UserHandle or accounts that no longer belongs to an"
- + " installed package.\n"
- + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
- + "\n"
- + "telecom set-phone-account-enabled: Enables the given phone account, if it has"
- + " already been registered with Telecom.\n"
- + "\n"
- + "telecom set-phone-account-disabled: Disables the given phone account, if it"
- + " has already been registered with telecom.\n"
- + "\n"
- + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
- + "telecom set-default-dialer: Sets the override default dialer to the given"
- + " component; this will override whatever the dialer role is set to.\n"
- + "\n"
- + "telecom get-default-dialer: Displays the current default dialer.\n"
- + "\n"
- + "telecom get-system-dialer: Displays the current system dialer.\n"
- + "telecom set-system-dialer: Set the override system dialer to the given"
- + " component. To remove the override, send \"default\"\n"
- + "\n"
- + "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
- + "\n"
- + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
- + " This may restart the device.\n"
- + "\n"
- + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
- + " or \"\" for single SIM\n"
- + "\n"
- + "telecom get-max-phones: Get the max supported phones from the modem.\n"
- + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
- + " package name that will be used for test emergency calls. To clear,"
- + " send an empty package name. Real emergency calls will still be placed"
- + " over Telephony.\n"
- + "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for "
- + "testers to indicate where in the logs various test steps take place.\n"
- + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
- + "non-ui-InCallService in InCallController to determine if it is bound \n"
- );
- }
-
- @Override
- public void onRun() throws Exception {
- mTelecomService = ITelecomService.Stub.asInterface(
- ServiceManager.getService(Context.TELECOM_SERVICE));
- if (mTelecomService == null) {
- Log.w(this, "onRun: Can't access telecom manager.");
- showError("Error: Could not access the Telecom Manager. Is the system running?");
- return;
- }
-
Looper.prepareMainLooper();
+ ITelecomService service = ITelecomService.Stub.asInterface(
+ ServiceManager.getService(Context.TELECOM_SERVICE));
Context context = ActivityThread.systemMain().getSystemContext();
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- if (mTelephonyManager == null) {
- Log.w(this, "onRun: Can't access telephony service.");
- showError("Error: Could not access the Telephony Service. Is the system running?");
- return;
- }
-
- mUserManager = IUserManager.Stub
- .asInterface(ServiceManager.getService(Context.USER_SERVICE));
- if (mUserManager == null) {
- Log.w(this, "onRun: Can't access user manager.");
- showError("Error: Could not access the User Manager. Is the system running?");
- return;
- }
- Log.i(this, "onRun: parsing command.");
- String command = nextArgRequired();
- switch (command) {
- case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
- runSetPhoneAccountEnabled(true);
- break;
- case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
- runSetPhoneAccountEnabled(false);
- break;
- case COMMAND_REGISTER_PHONE_ACCOUNT:
- runRegisterPhoneAccount();
- break;
- case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
- runSetTestCallRedirectionApp();
- break;
- case COMMAND_SET_TEST_CALL_SCREENING_APP:
- runSetTestCallScreeningApp();
- break;
- case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
- runAddOrRemoveCallCompanionApp();
- break;
- case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
- runSetTestPhoneAcctSuggestionComponent();
- break;
- case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
- runSetCallDiagnosticService();
- break;
- case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
- runRegisterSimPhoneAccount();
- break;
- case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
- runSetUserSelectedOutgoingPhoneAccount();
- break;
- case COMMAND_UNREGISTER_PHONE_ACCOUNT:
- runUnregisterPhoneAccount();
- break;
- case COMMAND_STOP_BLOCK_SUPPRESSION:
- runStopBlockSuppression();
- break;
- case COMMAND_CLEANUP_STUCK_CALLS:
- runCleanupStuckCalls();
- break;
- case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
- runCleanupOrphanPhoneAccounts();
- break;
- case COMMAND_RESET_CAR_MODE:
- runResetCarMode();
- break;
- case COMMAND_SET_DEFAULT_DIALER:
- runSetDefaultDialer();
- break;
- case COMMAND_GET_DEFAULT_DIALER:
- runGetDefaultDialer();
- break;
- case COMMAND_SET_SYSTEM_DIALER:
- runSetSystemDialer();
- break;
- case COMMAND_GET_SYSTEM_DIALER:
- runGetSystemDialer();
- break;
- case COMMAND_WAIT_ON_HANDLERS:
- runWaitOnHandler();
- break;
- case COMMAND_SET_SIM_COUNT:
- runSetSimCount();
- break;
- case COMMAND_GET_SIM_CONFIG:
- runGetSimConfig();
- break;
- case COMMAND_GET_MAX_PHONES:
- runGetMaxPhones();
- break;
- case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
- runIsNonUiInCallServiceBound();
- break;
- case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
- runSetEmergencyPhoneAccountPackageFilter();
- break;
- case COMMAND_LOG_MARK:
- runLogMark();
- break;
- default:
- Log.w(this, "onRun: unknown command: %s", command);
- throw new IllegalArgumentException ("unknown command '" + command + "'");
- }
- }
-
- private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final boolean success = mTelecomService.enablePhoneAccount(handle, enabled);
- if (success) {
- System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
- } else {
- System.out.println("Error - is " + handle + " a valid PhoneAccount?");
- }
- }
-
- private void runRegisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- PhoneAccount account = PhoneAccount.builder(handle, label)
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runRegisterSimPhoneAccount() throws RemoteException {
- boolean isEmergencyAccount = false;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-e": {
- isEmergencyAccount = true;
- break;
- }
- }
- }
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- final String address = nextArgRequired();
- int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
- | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
- | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
- PhoneAccount account = PhoneAccount.builder(
- handle, label)
- .setAddress(Uri.parse(address))
- .setSubscriptionAddress(Uri.parse(address))
- .setCapabilities(capabilities)
- .setShortDescription(label)
- .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
- .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
- .build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runSetTestCallRedirectionApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallRedirectionApp(packageName);
- }
-
- private void runSetTestCallScreeningApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallScreeningApp(packageName);
- }
-
- private void runAddOrRemoveCallCompanionApp() throws RemoteException {
- final String packageName = nextArgRequired();
- String isAdded = nextArgRequired();
- boolean isAddedBool = "1".equals(isAdded);
- mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
- }
-
- private void runSetCallDiagnosticService() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestCallDiagnosticService(packageName);
- System.out.println("Success - " + packageName + " set as call diagnostic service.");
- }
-
- private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
- final String componentName = nextArg();
- mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
- }
-
- private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
- Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
- System.out.println("Success - " + handle + " set as default outgoing account.");
- }
-
- private void runUnregisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " unregistered.");
- }
-
- private void runStopBlockSuppression() throws RemoteException {
- mTelecomService.stopBlockSuppression();
- }
-
- private void runCleanupStuckCalls() throws RemoteException {
- mTelecomService.cleanupStuckCalls();
- }
-
- private void runCleanupOrphanPhoneAccounts() throws RemoteException {
- System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts()
- + " phone accounts.");
- }
-
- private void runResetCarMode() throws RemoteException {
- mTelecomService.resetCarMode();
- }
-
- private void runSetDefaultDialer() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestDefaultDialer(packageName);
- System.out.println("Success - " + packageName + " set as override default dialer.");
- }
-
- private void runSetSystemDialer() throws RemoteException {
- final String flatComponentName = nextArg();
- final ComponentName componentName = (flatComponentName.equals("default")
- ? null : parseComponentName(flatComponentName));
- mTelecomService.setSystemDialer(componentName);
- System.out.println("Success - " + componentName + " set as override system dialer.");
- }
-
- private void runGetDefaultDialer() throws RemoteException {
- System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
- }
-
- private void runGetSystemDialer() throws RemoteException {
- System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
- }
-
- private void runWaitOnHandler() throws RemoteException {
-
- }
-
- private void runSetSimCount() throws RemoteException {
- if (!callerIsRoot()) {
- System.out.println("set-sim-count requires adb root");
- return;
- }
- int numSims = Integer.parseInt(nextArgRequired());
- System.out.println("Setting sim count to " + numSims + ". Device may reboot");
- mTelephonyManager.switchMultiSimConfig(numSims);
- }
-
- /**
- * prints out whether a particular non-ui InCallServices is bound in a call
- */
- public void runIsNonUiInCallServiceBound() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup.");
- } else {
- System.out.println(
- String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg())));
- }
- }
-
- /**
- * Prints the mSIM config to the console.
- * "DSDS" for a phone in DSDS mode
- * "" (empty string) for a phone in SS mode
- */
- private void runGetSimConfig() throws RemoteException {
- System.out.println(TelephonyProperties.multi_sim_config().orElse(""));
- }
-
- private void runGetMaxPhones() throws RemoteException {
- // how many logical modems can be potentially active simultaneously
- System.out.println(mTelephonyManager.getSupportedModemCount());
- }
-
- private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
- String packageName = mArgs.getNextArg();
- if (TextUtils.isEmpty(packageName)) {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
- System.out.println("Success - filter cleared");
- } else {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
- System.out.println("Success = filter set to " + packageName);
- }
-
- }
-
- private void runLogMark() throws RemoteException {
- String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" "));
- mTelecomService.requestLogMark(message);
- }
-
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- return null;
- }
- final ComponentName component = parseComponentName(nextArgRequired());
- final String accountId = nextArgRequired();
- final String userSnInStr = nextArgRequired();
- UserHandle userHandle;
- try {
- final int userSn = Integer.parseInt(userSnInStr);
- userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
- } catch (NumberFormatException ex) {
- Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
- throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
- }
- return new PhoneAccountHandle(component, accountId, userHandle);
- }
-
- private boolean callerIsRoot() {
- return Process.ROOT_UID == Process.myUid();
- }
-
- private ComponentName parseComponentName(String component) {
- ComponentName cn = ComponentName.unflattenFromString(component);
- if (cn == null) {
- throw new IllegalArgumentException ("Invalid component " + component);
- }
- return cn;
+ new TelecomShellCommand(service, context).exec(null, FileDescriptor.in,
+ FileDescriptor.out, FileDescriptor.err, args);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 42ac6b73f61f..b9ad92b77c6f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5395,7 +5395,7 @@ package android.app {
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+ ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -6674,9 +6674,9 @@ package android.app {
method @Deprecated public android.app.Notification.Builder addPerson(String);
method @NonNull public android.app.Notification.Builder addPerson(android.app.Person);
method @NonNull public android.app.Notification build();
- method public android.widget.RemoteViews createBigContentView();
- method public android.widget.RemoteViews createContentView();
- method public android.widget.RemoteViews createHeadsUpContentView();
+ method @Deprecated public android.widget.RemoteViews createBigContentView();
+ method @Deprecated public android.widget.RemoteViews createContentView();
+ method @Deprecated public android.widget.RemoteViews createHeadsUpContentView();
method @NonNull public android.app.Notification.Builder extend(android.app.Notification.Extender);
method public android.os.Bundle getExtras();
method @Deprecated public android.app.Notification getNotification();
@@ -9608,7 +9608,7 @@ package android.appwidget {
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
- method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public boolean setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -15708,7 +15708,7 @@ package android.graphics {
method public boolean clipRect(int, int, int, int);
method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader);
method public void concat(@Nullable android.graphics.Matrix);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16361,7 +16361,7 @@ package android.graphics {
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16370,7 +16370,7 @@ package android.graphics {
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
}
@@ -41153,19 +41153,19 @@ package android.service.notification {
method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
method public android.os.IBinder onBind(android.content.Intent);
- method public void onInterruptionFilterChanged(int);
- method public void onListenerConnected();
- method public void onListenerDisconnected();
- method public void onListenerHintsChanged(int);
- method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
- method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
- method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+ method @UiThread public void onInterruptionFilterChanged(int);
+ method @UiThread public void onListenerConnected();
+ method @UiThread public void onListenerDisconnected();
+ method @UiThread public void onListenerHintsChanged(int);
+ method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+ method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+ method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public static void requestRebind(android.content.ComponentName);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d190c62413e0..b73f1993ee4c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,7 +70,7 @@ package android {
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
- field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE";
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
@@ -403,7 +403,6 @@ package android {
field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
@@ -2223,10 +2222,9 @@ package android.app.ondeviceintelligence {
}
public static final class Feature.Builder {
- ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+ ctor public Feature.Builder(int);
method @NonNull public android.app.ondeviceintelligence.Feature build();
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
@@ -2238,7 +2236,7 @@ package android.app.ondeviceintelligence {
ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+ method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2251,27 +2249,16 @@ package android.app.ondeviceintelligence {
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
- method public int describeContents();
- method @NonNull public java.io.FileInputStream getFileInputStream();
- method @NonNull public String getFilePartKey();
- method @NonNull public android.os.PersistableBundle getFilePartParams();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -2305,6 +2292,10 @@ package android.app.ondeviceintelligence {
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
}
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ }
+
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
ctor public ProcessingSignal();
method public void sendSignal(@NonNull android.os.PersistableBundle);
@@ -2315,8 +2306,18 @@ package android.app.ondeviceintelligence {
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
- method public void onNewContent(@NonNull T);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
+ method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
+ ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
+ ctor public TokenInfo(long);
+ method public int describeContents();
+ method public long getCount();
+ method @NonNull public android.os.PersistableBundle getInfoParams();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
}
}
@@ -3759,7 +3760,6 @@ package android.content {
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
field public static final String FONT_SERVICE = "font";
field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -5137,15 +5137,6 @@ package android.hardware.display {
}
-package android.hardware.face {
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
- }
-
-}
-
package android.hardware.hdmi {
public abstract class HdmiClient {
@@ -11537,7 +11528,7 @@ package android.permission {
method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -12891,7 +12882,7 @@ package android.service.notification {
}
public abstract class NotificationListenerService extends android.app.Service {
- method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+ method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
}
public static class NotificationListenerService.Ranking {
@@ -12963,12 +12954,14 @@ package android.service.ondeviceintelligence {
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
- method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onInferenceServiceConnected();
+ method public abstract void onInferenceServiceDisconnected();
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
@@ -12985,17 +12978,18 @@ package android.service.ondeviceintelligence {
field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
- ctor public OnDeviceTrustedInferenceService();
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
+ ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+ method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
- field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
}
}
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1923641e2d4e..1e72a061d35a 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -511,9 +511,9 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String) parameter #0:
Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
@@ -571,7 +571,7 @@ MissingNullability: android.service.contentcapture.ContentCaptureService#dump(ja
Missing nullability on parameter `args` in method `dump`
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
Missing nullability on parameter `base` in method `attachBaseContext`
-MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+MissingNullability: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String):
Missing nullability on method `openFileInput` return
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
Missing nullability on parameter `intent` in method `onUnbind`
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6ec370478a9..5e2397d41424 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@ public final class AutomaticZenRule implements Parcelable {
* interrupt the user (e.g. via sound &amp; vibration) while this rule
* is active.
* @param enabled Whether the rule is enabled.
- * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
- * ZenPolicy, int, boolean)}.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
@Deprecated
public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -206,8 +206,10 @@ public final class AutomaticZenRule implements Parcelable {
* while this rule is active. This overrides the global policy while this rule is
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
- // TODO (b/309088420): deprecate this constructor in favor of the builder
+ @Deprecated
public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
@Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
@Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +370,9 @@ public final class AutomaticZenRule implements Parcelable {
/**
* Sets the zen policy.
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous policy is retained.
*/
public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +395,12 @@ public final class AutomaticZenRule implements Parcelable {
* Sets the configuration activity - an activity that handles
* {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
* about this rule and/or allows them to configure it. This is required to be non-null for rules
- * that are not backed by {@link android.service.notification.ConditionProviderService}.
+ * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+ *
+ * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+ * configuration activity is set will not use the
+ * {@link android.service.notification.ConditionProviderService} supplied there to determine
+ * whether the rule should be active.
*/
public void setConfigurationActivity(@Nullable ComponentName componentName) {
this.configurationActivity = getTrimmedComponentName(componentName);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1129f9dbdfeb..79cb09d5baea 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6068,11 +6068,19 @@ public class Notification implements Parcelable
}
/**
- * Construct a RemoteViews for the final 1U notification layout. In order:
- * 1. Custom contentView from the caller
- * 2. Style's proposed content view
- * 3. Standard template view
+ * Construct a RemoteViews representing the standard notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createContentView() {
return createContentView(false /* increasedheight */ );
}
@@ -6181,8 +6189,19 @@ public class Notification implements Parcelable
}
/**
- * Construct a RemoteViews for the final big notification layout.
+ * Construct a RemoteViews representing the expanded notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createBigContentView() {
RemoteViews result = null;
if (useExistingRemoteView(mN.bigContentView)) {
@@ -6315,8 +6334,19 @@ public class Notification implements Parcelable
}
/**
- * Construct a RemoteViews for the final heads-up notification layout.
+ * Construct a RemoteViews representing the heads up notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createHeadsUpContentView() {
return createHeadsUpContentView(false /* useIncreasedHeight */);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 283e21aab39c..b82a1e3d8dfa 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@ public class NotificationManager {
* Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
* the {@link AutomaticZenRule}.
*
- * <p>
- * The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
- * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
- * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
- * </p>
+ * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+ * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+ * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+ * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+ *
+ * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+ * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+ * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
*/
- // TODO (b/309101513): Add new status types to javadoc
public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
"android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
@@ -370,11 +373,15 @@ public class NotificationManager {
= "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
/**
- * Intent that is broadcast when the state of getNotificationPolicy() changes.
+ * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
*
* <p>This broadcast is only sent to registered receivers and (starting from
* {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
* Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+ *
+ * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+ * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+ * the global policy, so this broadcast will be sent much less frequently.
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@ public class NotificationManager {
/**
* Updates the given zen rule.
*
- * <p>
- * Throws a SecurityException if policy access is not granted to this package.
+ * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+ * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+ * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+ * will only happen if the rule's definition is actually changing.
+ *
+ * <p>Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
- * <p>
- * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ *
* @param id The id of the rule to update
* @param automaticZenRule the rule to update.
* @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@ public class NotificationManager {
/**
* Gets the current user-specified default notification policy.
*
- * <p>
+ * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) this method will return the policy associated
+ * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+ * {@link #setNotificationPolicy(Policy)}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public Policy getNotificationPolicy() {
INotificationManager service = getService();
try {
@@ -1757,15 +1770,20 @@ public class NotificationManager {
}
/**
- * Sets the current notification policy.
+ * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+ * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
*
- * <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global notification policy.
+ * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+ * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+ * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+ *
+ * <p>Only available if policy access is granted to this package. See
+ * {@link #isNotificationPolicyAccessGranted}.
*
* @param policy The new desired policy.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
setNotificationPolicy(policy, /* fromUser= */ false);
}
@@ -2786,11 +2804,17 @@ public class NotificationManager {
* The interruption filter defines which notifications are allowed to
* interrupt the user (e.g. via sound &amp; vibration) and is applied
* globally.
- * <p>
- * Only available if policy access is granted to this package. See
+ *
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+ * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+ * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+ * provided).
+ *
+ * <p> Only available if policy access is granted to this package. See
* {@link #isNotificationPolicyAccessGranted}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index fb1b17bd23d4..89199ca0d493 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,7 @@
package android.app.assist;
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
import android.annotation.FlaggedApi;
@@ -20,14 +22,17 @@ import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PooledStringReader;
import android.os.PooledStringWriter;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.service.autofill.FillRequest;
import android.service.credentials.CredentialProviderService;
@@ -37,6 +42,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.View;
import android.view.View.AutofillImportance;
import android.view.ViewRootImpl;
@@ -652,6 +658,9 @@ public class AssistStructure implements Parcelable {
@Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException>
mGetCredentialCallback;
+ @Nullable ResultReceiver mGetCredentialResultReceiver;
+
+
AutofillValue mAutofillValue;
CharSequence[] mAutofillOptions;
boolean mSanitized;
@@ -916,6 +925,7 @@ public class AssistStructure implements Parcelable {
mExtras = in.readBundle();
}
mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
+ mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
}
/**
@@ -1153,6 +1163,7 @@ public class AssistStructure implements Parcelable {
out.writeBundle(mExtras);
}
out.writeTypedObject(mGetCredentialRequest, flags);
+ out.writeTypedObject(mGetCredentialResultReceiver, flags);
return flags;
}
@@ -1295,9 +1306,8 @@ public class AssistStructure implements Parcelable {
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredentialManagerCallback() {
- return mGetCredentialCallback;
+ public ResultReceiver getCredentialManagerCallback() {
+ return mGetCredentialResultReceiver;
}
/**
@@ -1894,6 +1904,7 @@ public class AssistStructure implements Parcelable {
final AssistStructure mAssist;
final ViewNode mNode;
final boolean mAsync;
+ private Handler mHandler;
/**
* Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated
@@ -2271,6 +2282,56 @@ public class AssistStructure implements Parcelable {
option.getCandidateQueryData()
.putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
}
+ setUpResultReceiver(callback);
+ }
+
+ private void setUpResultReceiver(
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+ final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+ Slog.d(TAG, "onReceiveResult from Credential Manager");
+ GetCredentialResponse getCredentialResponse =
+ resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ GetCredentialResponse.class);
+
+ callback.onResult(getCredentialResponse);
+ } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+ String[] exception = resultData.getStringArray(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+ if (exception != null && exception.length >= 2) {
+ Slog.w(TAG, "Credman bottom sheet from pinned "
+ + "entry failed with: + " + exception[0] + " , "
+ + exception[1]);
+ callback.onError(new GetCredentialException(
+ exception[0], exception[1]));
+ }
+ } else {
+ Slog.d(TAG, "Unknown resultCode from credential "
+ + "manager bottom sheet: " + resultCode);
+ }
+ }
+ };
+ ResultReceiver ipcFriendlyResultReceiver =
+ toIpcFriendlyResultReceiver(resultReceiver);
+ mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver;
+ }
+
+ private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
}
@Override
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 510735461553..4a38c9224a3d 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -199,24 +199,13 @@ public final class Feature implements Parcelable {
private long mBuilderFieldsSet = 0L;
- public Builder(
- int id,
- int type,
- int variant,
- @NonNull PersistableBundle featureParams) {
+ /**
+ * Provides a builder instance to create a feature for given id.
+ * @param id the unique identifier for the feature.
+ */
+ public Builder(int id) {
mId = id;
- mType = type;
- mVariant = variant;
- mFeatureParams = featureParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureParams);
- }
-
- public @NonNull Builder setId(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mId = value;
- return this;
+ mFeatureParams = new PersistableBundle();
}
public @NonNull Builder setName(@NonNull String value) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index 92f351362f70..f3cbd2621694 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -41,7 +41,7 @@ import java.text.MessageFormat;
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class FeatureDetails implements Parcelable {
@Status
- private final int mStatus;
+ private final int mFeatureStatus;
@NonNull
private final PersistableBundle mFeatureDetailParams;
@@ -73,21 +73,21 @@ public final class FeatureDetails implements Parcelable {
}
public FeatureDetails(
- @Status int status,
+ @Status int featureStatus,
@NonNull PersistableBundle featureDetailParams) {
- this.mStatus = status;
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = featureDetailParams;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
}
public FeatureDetails(
- @Status int status) {
- this.mStatus = status;
+ @Status int featureStatus) {
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = new PersistableBundle();
}
@@ -95,8 +95,8 @@ public final class FeatureDetails implements Parcelable {
/**
* Returns an integer value associated with the feature status.
*/
- public @Status int getStatus() {
- return mStatus;
+ public @Status int getFeatureStatus() {
+ return mFeatureStatus;
}
@@ -111,7 +111,7 @@ public final class FeatureDetails implements Parcelable {
public String toString() {
return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ "persistableBundle = {1} '}'",
- mStatus,
+ mFeatureStatus,
mFeatureDetailParams);
}
@@ -121,21 +121,21 @@ public final class FeatureDetails implements Parcelable {
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
FeatureDetails that = (FeatureDetails) o;
- return mStatus == that.mStatus
+ return mFeatureStatus == that.mFeatureStatus
&& java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
}
@Override
public int hashCode() {
int _hash = 1;
- _hash = 31 * _hash + mStatus;
+ _hash = 31 * _hash + mFeatureStatus;
_hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
return _hash;
}
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- dest.writeInt(mStatus);
+ dest.writeInt(mFeatureStatus);
dest.writeTypedObject(mFeatureDetailParams, flags);
}
@@ -151,9 +151,9 @@ public final class FeatureDetails implements Parcelable {
PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
PersistableBundle.CREATOR);
- this.mStatus = status;
+ this.mFeatureStatus = status;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = persistableBundle;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
deleted file mode 100644
index e9fb5f2cb440..000000000000
--- a/core/java/android/app/ondeviceintelligence/FilePart.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import android.annotation.NonNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Represents file data with an associated file descriptor sent to and received from remote
- * processing. The interface ensures that the underlying file-descriptor is always opened in
- * read-only mode.
- *
- * @hide
- */
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-@SystemApi
-public final class FilePart implements Parcelable {
- private final String mPartKey;
- private final PersistableBundle mPartParams;
- private final ParcelFileDescriptor mParcelFileDescriptor;
-
- private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull ParcelFileDescriptor parcelFileDescriptor) {
- Objects.requireNonNull(partKey);
- Objects.requireNonNull(partParams);
- this.mPartKey = partKey;
- this.mPartParams = partParams;
- this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
- }
-
- /**
- * Create a file part using a filePath and any additional params.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull String filePath)
- throws FileNotFoundException {
- this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
- new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
- }
-
- /**
- * Create a file part using a file input stream and any additional params.
- * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
- * call returns.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull FileInputStream fileInputStream)
- throws IOException {
- this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
- }
-
- /**
- * Returns a FileInputStream for the associated File.
- * Caller must close the associated stream when done reading from it.
- *
- * @return the FileInputStream associated with the FilePart.
- */
- @NonNull
- public FileInputStream getFileInputStream() {
- return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
- }
-
- /**
- * Returns the unique key associated with the part. Each Part key added to a content object
- * should be ensured to be unique.
- */
- @NonNull
- public String getFilePartKey() {
- return mPartKey;
- }
-
- /**
- * Returns the params associated with Part.
- */
- @NonNull
- public PersistableBundle getFilePartParams() {
- return mPartParams;
- }
-
-
- @Override
- public int describeContents() {
- return CONTENTS_FILE_DESCRIPTOR;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString8(getFilePartKey());
- dest.writePersistableBundle(getFilePartParams());
- mParcelFileDescriptor.writeToParcel(dest, flags
- | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
- // copy of the Pfd is closed as soon as the Binder call succeeds.
- }
-
- @NonNull
- public static final Creator<FilePart> CREATOR = new Creator<>() {
- @Override
- public FilePart createFromParcel(Parcel in) {
- return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
- in.readParcelable(
- getClass().getClassLoader(), ParcelFileDescriptor.class));
- }
-
- @Override
- public FilePart[] newArray(int size) {
- return new FilePart[size];
- }
- };
-}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index b925f4863de2..360a8094723c 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -31,7 +31,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
- import android.app.ondeviceintelligence.ITokenCountCallback;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
/**
@@ -56,8 +56,8 @@
void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal,
- in ITokenCountCallback tokenCountcallback) = 6;
+ void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 9848e1ddda5a..0adf305f2920 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -3,6 +3,7 @@ package android.app.ondeviceintelligence;
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
* Interface for a IResponseCallback for receiving response from on-device intelligence service.
@@ -12,4 +13,5 @@ import android.os.PersistableBundle;
interface IResponseCallback {
void onSuccess(in Content result) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index a6805749fa04..132e53e1ae2e 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -4,6 +4,7 @@ import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
@@ -15,4 +16,5 @@ interface IStreamingResponseCallback {
void onNewContent(in Content result) = 1;
void onSuccess(in Content result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
deleted file mode 100644
index b724e03fbca4..000000000000
--- a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.app.ondeviceintelligence;
-
-import android.os.PersistableBundle;
-
-/**
- * Interface for receiving the token count of a request for a given features.
- *
- * @hide
- */
-interface ITokenCountCallback {
- void onSuccess(long tokenCount) = 1;
- void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 000000000000..9219a89128df
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+ * Interface for receiving the token info of a request for a given feature.
+ *
+ * @hide
+ */
+interface ITokenInfoCallback {
+ void onSuccess(in TokenInfo tokenInfo) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 4d8e0d55e355..d195c4d52c22 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -26,8 +26,10 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@ import android.os.RemoteException;
import androidx.annotation.IntDef;
+import com.android.internal.R;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,7 +64,16 @@ import java.util.function.LongConsumer;
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public class OnDeviceIntelligenceManager {
+ /**
+ * @hide
+ */
public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+ /**
+ * @hide
+ */
+ public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+ "AugmentRequestContentBundleKey";
private final Context mContext;
private final IOnDeviceIntelligenceManager mService;
@@ -82,8 +95,6 @@ public class OnDeviceIntelligenceManager {
public void getVersion(
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull LongConsumer versionConsumer) {
- // TODO explore modifying this method into getServicePackageDetails and return both
- // version and package name of the remote service implementing this.
try {
RemoteCallback callback = new RemoteCallback(result -> {
if (result == null) {
@@ -100,6 +111,23 @@ public class OnDeviceIntelligenceManager {
}
}
+
+ /**
+ * Get package name configured for providing the remote implementation for this system service.
+ */
+ @Nullable
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public String getRemoteServicePackageName() {
+ String serviceConfigValue = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
+ ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+
+ return null;
+ }
+
/**
* Asynchronously get feature for a given id.
*
@@ -273,29 +301,29 @@ public class OnDeviceIntelligenceManager {
}
/**
- * The methods computes the token-count for a given request payload using the provided Feature
- * details.
+ * The methods computes the token related information for a given request payload using the
+ * provided {@link Feature}.
*
* @param feature feature associated with the request.
* @param request request that contains the content data and associated params.
- * @param outcomeReceiver callback to populate the token count or exception in case of
+ * @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
* implementation.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManagerException> outcomeReceiver) {
try {
- ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+ ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
- public void onSuccess(long tokenCount) {
+ public void onSuccess(TokenInfo tokenInfo) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> outcomeReceiver.onResult(tokenCount)));
+ () -> outcomeReceiver.onResult(tokenInfo)));
}
@Override
@@ -314,7 +342,7 @@ public class OnDeviceIntelligenceManager {
cancellationSignal.setRemote(transport);
}
- mService.requestTokenCount(feature, request, transport, callback);
+ mService.requestTokenInfo(feature, request, transport, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -328,33 +356,31 @@ public class OnDeviceIntelligenceManager {
* was a
* failure.
*
- * @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
- * @param requestType type of request being sent for processing the content.
- * @param responseOutcomeReceiver callback to populate the response content and
- * associated
- * params.
- * @param processingSignal signal to invoke custom actions in the
- * remote implementation.
- * @param cancellationSignal signal to invoke cancellation or
- * @param callbackExecutor executor to run the callback on.
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and
+ * associated params.
+ * @param requestType type of request being sent for processing the content.
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
+ * remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ * @param responseCallback callback to populate the response content and
+ * associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @NonNull Content request,
+ public void processRequest(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+ @NonNull ProcessingOutcomeReceiver responseCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+ callbackExecutor.execute(() -> responseCallback.onResult(result));
});
}
@@ -362,12 +388,24 @@ public class OnDeviceIntelligenceManager {
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseOutcomeReceiver.onError(
+ () -> responseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams))));
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> responseCallback.onDataAugmentRequest(content, result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+ callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+ })));
+ }
};
+
IProcessingSignal transport = null;
if (processingSignal != null) {
transport = ProcessingSignal.createTransport();
@@ -389,46 +427,48 @@ public class OnDeviceIntelligenceManager {
}
/**
- * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+ * Variation of {@link #processRequest} that asynchronously processes a request in a
+ * streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
- * populate the complete {@link Response}'s Content as part of the callback when the final
- * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+ * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
+ * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
+ * populate the complete the full response {@link Content} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the Contents already
+ * streamed.
*
* @param feature feature associated with the request.
* @param request request that contains the Content data and associated
* params.
* @param requestType type of request being sent for processing the content.
- * @param processingSignal signal to invoke other custom actions in the
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
* remote implementation.
- * @param cancellationSignal signal to invoke cancellation
- * @param streamingResponseReceiver streaming callback to populate the response content and
+ * @param streamingResponseCallback streaming callback to populate the response content and
* associated params.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+ @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(Content result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onNewContent(result));
+ () -> streamingResponseCallback.onNewContent(result));
});
}
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+ callbackExecutor.execute(
+ () -> streamingResponseCallback.onResult(result));
});
}
@@ -437,11 +477,26 @@ public class OnDeviceIntelligenceManager {
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onError(
+ () -> streamingResponseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams)));
});
}
+
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> streamingResponseCallback.onDataAugmentRequest(content,
+ contentResponse -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ contentResponse);
+ callbackExecutor.execute(
+ () -> contentCallback.sendResult(bundle));
+ })));
+ }
};
IProcessingSignal transport = null;
@@ -468,7 +523,8 @@ public class OnDeviceIntelligenceManager {
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
- * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc
+ * .which
* are time consuming beforehand to remove overhead and allow quick processing of requests
* thereof.
*/
@@ -485,7 +541,8 @@ public class OnDeviceIntelligenceManager {
REQUEST_TYPE_PREPARE,
REQUEST_TYPE_EMBEDDINGS
}, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+ ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestType {
}
@@ -501,17 +558,32 @@ public class OnDeviceIntelligenceManager {
*/
public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
+ /**
+ * Error code to be used for on device intelligence manager failures.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceManagerErrorCode {
+ }
+
private final int mErrorCode;
private final PersistableBundle errorParams;
- public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
@NonNull PersistableBundle errorParams) {
super(errorMessage);
this.mErrorCode = errorCode;
this.errorParams = errorParams;
}
- public OnDeviceIntelligenceManagerException(int errorCode,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode,
@NonNull PersistableBundle errorParams) {
this.mErrorCode = errorCode;
this.errorParams = errorParams;
@@ -573,11 +645,15 @@ public class OnDeviceIntelligenceManager {
/** Inference suspended so that higher-priority inference can run. */
public static final int PROCESSING_ERROR_SUSPENDED = 13;
- /** Underlying processing encountered an internal error, like a violated precondition. */
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
public static final int PROCESSING_ERROR_INTERNAL = 14;
/**
- * The processing was not able to be passed on to the remote implementation, as the service
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
* was unavailable.
*/
public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
new file mode 100644
index 000000000000..b0b6e1948cf0
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+import java.util.function.Consumer;
+
+/**
+ * Response Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial {@link Content} that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingOutcomeReceiver extends
+ OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param content The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request.
+ */
+ default void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentConsumer) {
+ contentConsumer.accept(null);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
index ebcf61c8c0c2..ac2b0329496c 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
@@ -21,23 +21,19 @@ import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
/**
* Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in
- * chunks to provide a async processing behaviour to the caller.
+ * possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
- OutcomeReceiver<R, E> {
+public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
/**
- * Callback to be invoked when a part of the response i.e. some {@link Content} is already
- * processed and
- * needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some {@link Content} is
+ * already processed and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull T content);
+ void onNewContent(@NonNull Content content);
}
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
new file mode 100644
index 000000000000..2c19c1eb246c
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/core/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 000000000000..035cc4b365b5
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+ private final long mCount;
+ private final PersistableBundle mInfoParams;
+
+ /**
+ * Construct a token count using the count value and associated params.
+ */
+ public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+ this.mCount = count;
+ mInfoParams = persistableBundle;
+ }
+
+ /**
+ * Construct a token count using the count value.
+ */
+ public TokenInfo(long count) {
+ this.mCount = count;
+ this.mInfoParams = new PersistableBundle();
+ }
+
+ /**
+ * Returns the token count associated with a request payload.
+ */
+ public long getCount() {
+ return mCount;
+ }
+
+ /**
+ * Returns the params representing token info.
+ */
+ @NonNull
+ public PersistableBundle getInfoParams() {
+ return mInfoParams;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mCount);
+ dest.writePersistableBundle(mInfoParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+ = new Parcelable.Creator<>() {
+ @Override
+ public TokenInfo[] newArray(int size) {
+ return new TokenInfo[size];
+ }
+
+ @Override
+ public TokenInfo createFromParcel(@NonNull Parcel in) {
+ return new TokenInfo(in.readLong(), in.readPersistableBundle());
+ }
+ };
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb82e1fbb16c..cda4d89b828f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1417,13 +1417,15 @@ public class AppWidgetManager {
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+ *
+ * @return true if the call was successful, false if it was rate-limited.
*/
@FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
- public void setWidgetPreview(@NonNull ComponentName provider,
+ public boolean setWidgetPreview(@NonNull ComponentName provider,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
try {
- mService.setWidgetPreview(provider, widgetCategories, preview);
+ return mService.setWidgetPreview(provider, widgetCategories, preview);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 70d2c7a3a41a..c7e5d88c299d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5081,8 +5081,6 @@ public abstract class Context {
* @see #getSystemService
* @see android.hardware.face.FaceManager
*/
- @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
public static final String FACE_SERVICE = "face";
/**
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 4dcc51729a61..a9084565180a 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -163,14 +163,31 @@ public final class DomainVerificationManager {
}
/**
- * Update the URI relative filter groups for a package. All previously existing groups
- * will be cleared before the new groups will be applied.
+ * Update the URI relative filter groups for a package. The groups set using this API acts
+ * as an additional filtering layer during intent resolution. It does not replace any
+ * existing groups that have been added to the package's intent filters either using the
+ * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)}
+ * API or defined in the manifest.
+ * <p>
+ * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the
+ * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups
+ * that are indexed to the specific subdomain of the intent's data URI followed by any groups
+ * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority
+ * will decrease corresponding to the decreasing number of subdomain labels after the wildcard.
+ * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d".
+ * <p>
+ * All previously existing groups set for a domain index using this API will be cleared when
+ * new groups are set.
*
* @param packageName The name of the package.
* @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
* should apply to them. Groups for each domain will replace any groups
- * provided for that domain in a prior call to this method. Groups will
+ * provided for that domain in a prior call to this method. To clear
+ * existing groups, set the list to null or a empty list. Groups will
* be evaluated in the order they are provided.
+ *
+ * @see UriRelativeFilterGroup
+ * @see android.content.IntentFilter
* @hide
*/
@SystemApi
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 08b906439b08..4cdaaddd05bf 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -727,7 +727,7 @@ public final class SensorPrivacyManager {
* Returns if camera privacy is enabled for a specific package.
*
* @param packageName The package to check
- * @return boolean sensor privacy state.
+ * @return boolean camera privacy state.
*
* @hide
*/
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 8165d44b251a..3ba8be4cc2ab 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -28,10 +28,3 @@ flag {
bug: "302735104"
}
-flag {
- name: "face_background_authentication"
- namespace: "biometrics_framework"
- description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
- bug: "318584190"
-}
-
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 066c45f03ec4..210ce2b78fca 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,23 +18,18 @@ package android.hardware.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -42,9 +37,9 @@ import android.os.Binder;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -54,21 +49,15 @@ import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* A class that coordinates access to the face authentication hardware.
- *
- * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
- * customized for unique system-level utilities, like the lock screen or ambient background usage.
- *
* @hide
*/
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-@SystemApi
@SystemService(Context.FACE_SERVICE)
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
@@ -99,76 +88,81 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
@Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
private CryptoObject mCryptoObject;
private Face mRemovalFace;
- private Executor mExecutor;
+ private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mExecutor.execute(() -> sendEnrollResult(face, remaining));
+ mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
+ mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+ isStrongBiometric ? 1 : 0, face).sendToTarget();
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
+ .sendToTarget();
}
@Override // binder call
public void onAuthenticationFailed() {
- mExecutor.execute(() -> sendAuthenticatedFailed());
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mExecutor.execute(() -> sendErrorResult(error, vendorCode));
+ mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mExecutor.execute(() -> {
- sendRemovedResult(face, remaining);
- if (remaining == 0) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
- UserHandle.USER_CURRENT);
- }
- });
+ mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ if (remaining == 0) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+ UserHandle.USER_CURRENT);
+ }
}
@Override
public void onFeatureSet(boolean success, int feature) {
- mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
+ mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = success;
+ args.arg2 = features;
+ args.arg3 = featureState;
+ mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
+ mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
+ .sendToTarget();
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mExecutor.execute(() -> sendAuthenticationFrame(frame));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mExecutor.execute(() -> sendEnrollmentFrame(frame));
+ mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
}
};
@@ -181,7 +175,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mExecutor = context.getMainExecutor();
+ mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -195,16 +189,18 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
- * {@code handler} is {@code null}.
+ * Use the provided handler thread for events.
*/
- private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
- return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
+ private void useHandler(Handler handler) {
+ if (handler != null) {
+ mHandler = new MyHandler(handler.getLooper());
+ } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+ mHandler = new MyHandler(mContext.getMainLooper());
+ }
}
/**
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
- * @hide
*/
@Deprecated
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -216,22 +212,17 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Request authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
+ * Request authentication. This call operates the face recognition hardware and starts capturing images.
* It terminates when
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
* which point the object is no longer valid. The operation can be canceled by using the
- * provided {@code cancel} object.
+ * provided cancel object.
*
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed
+ * @param crypto object associated with the call or null if none required
+ * @param cancel an object that can be used to cancel authentication
* @param callback an object to receive authentication events
- * @param handler an optional handler to handle callback events or {@code null} to obtain main
- * {@link Executor} from {@link Context}
+ * @param handler an optional handler to handle callback events
* @param options additional options to customize this request
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
* by
@@ -244,14 +235,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
@NonNull AuthenticationCallback callback, @Nullable Handler handler,
@NonNull FaceAuthenticateOptions options) {
- authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
- options, false /* allowBackgroundAuthentication */);
- }
-
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull AuthenticationCallback callback, @NonNull Executor executor,
- @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
if (callback == null) {
throw new IllegalArgumentException("Must supply an authentication callback");
}
@@ -266,15 +249,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService != null) {
try {
- mExecutor = executor;
+ useHandler(handler);
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- final long authId = allowBackgroundAuthentication
- ? mService.authenticateInBackground(
- mToken, operationId, mServiceReceiver, options)
- : mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(
+ mToken, operationId, mServiceReceiver, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -292,67 +273,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Request background face authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
- * It terminates when
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
- * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
- * valid. The operation can be canceled by using the provided cancel object.
- *
- * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
- * {@link BiometricPrompt} for face authentication unless the experience must be customized for
- * unique system-level utilities, like the lock screen or ambient background usage.
- *
- * @param executor the specified {@link Executor} to handle callback events; if {@code null},
- * the callback will be executed on the main {@link Executor}.
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required.
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed.
- * @param callback an object to receive authentication events.
- * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
- * by
- * <a href="{@docRoot}training/articles/keystore.html">Android
- * Keystore facility</a>.
- * @hide
- */
- @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
- public void authenticateInBackground(@Nullable Executor executor,
- @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull BiometricPrompt.AuthenticationCallback callback) {
- authenticate(crypto, cancel, new AuthenticationCallback() {
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- callback.onAuthenticationError(errorCode, errString);
- }
-
- @Override
- public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
- callback.onAuthenticationHelp(helpCode, helpString);
- }
-
- @Override
- public void onAuthenticationSucceeded(AuthenticationResult result) {
- callback.onAuthenticationSucceeded(
- new BiometricPrompt.AuthenticationResult(
- crypto,
- BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
- }
-
- @Override
- public void onAuthenticationFailed() {
- callback.onAuthenticationFailed();
- }
- }, executor == null ? mContext.getMainExecutor() : executor,
- new FaceAuthenticateOptions.Builder().build(),
- true /* allowBackgroundAuthentication */);
- }
-
- /**
* Uses the face hardware to detect for the presence of a face, without giving details about
* accept/reject/lockout.
* @hide
@@ -710,14 +630,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Determine if there are enrolled {@link Face} templates.
+ * Determine if there is a face enrolled.
*
- * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
+ * @return true if a face is enrolled, false otherwise
* @hide
*/
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledTemplates() {
return hasEnrolledTemplates(UserHandle.myUserId());
}
@@ -882,7 +800,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
PowerManager.PARTIAL_WAKE_LOCK,
"faceLockoutResetCallback");
wakeLock.acquire();
- mExecutor.execute(() -> {
+ mHandler.post(() -> {
try {
callback.onLockoutReset(sensorId);
} finally {
@@ -1352,6 +1270,70 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
+ private class MyHandler extends Handler {
+ private MyHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ private MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
+ switch (msg.what) {
+ case MSG_ENROLL_RESULT:
+ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_ACQUIRED:
+ sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_AUTHENTICATION_SUCCEEDED:
+ sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+ msg.arg2 == 1 /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FAILED:
+ sendAuthenticatedFailed();
+ break;
+ case MSG_ERROR:
+ sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_REMOVED:
+ sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_SET_FEATURE_COMPLETED:
+ sendSetFeatureCompleted((boolean) msg.obj /* success */,
+ msg.arg1 /* feature */);
+ break;
+ case MSG_GET_FEATURE_COMPLETED:
+ SomeArgs args = (SomeArgs) msg.obj;
+ sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+ (int[]) args.arg2 /* features */,
+ (boolean[]) args.arg3 /* featureState */);
+ args.recycle();
+ break;
+ case MSG_CHALLENGE_GENERATED:
+ sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (long) msg.obj /* challenge */);
+ break;
+ case MSG_FACE_DETECTED:
+ sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (boolean) msg.obj /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FRAME:
+ sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+ break;
+ case MSG_ENROLLMENT_FRAME:
+ sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ Trace.endSection();
+ }
+ }
+
private void sendSetFeatureCompleted(boolean success, int feature) {
if (mSetFeatureCallback == null) {
return;
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b98c0cb41ac9..553d9f76bd01 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -47,7 +47,7 @@ interface IFaceService {
byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
// Retrieve static sensor properties for all face sensors
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Retrieve static sensor properties for the specified sensor
@@ -59,11 +59,6 @@ interface IFaceService {
long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
in FaceAuthenticateOptions options);
- // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
- @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
- long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
- in FaceAuthenticateOptions options);
-
// Uses the face hardware to detect for the presence of a face, without giving details
// about accept/reject/lockout. A requestId is returned that can be used to cancel this
// operation.
@@ -138,7 +133,7 @@ interface IFaceService {
void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
// Determine if a user has at least one enrolled face
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
// Return the LockoutTracker status for the specified user
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a14a2c7e54ca..555a1204c8a7 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,6 +70,8 @@ public class VibrationConfig {
private final boolean mDefaultKeyboardVibrationEnabled;
+ private final boolean mHasFixedKeyboardAmplitude;
+
/** @hide */
public VibrationConfig(@Nullable Resources resources) {
mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -87,6 +89,8 @@ public class VibrationConfig {
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
+ mHasFixedKeyboardAmplitude = loadFloat(resources,
+ com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -197,6 +201,14 @@ public class VibrationConfig {
return mDefaultKeyboardVibrationEnabled;
}
+ /**
+ * Whether the device has a fixed amplitude for keyboard.
+ * @hide
+ */
+ public boolean hasFixedKeyboardAmplitude() {
+ return mHasFixedKeyboardAmplitude;
+ }
+
/** Get the default vibration intensity for given usage. */
@VibrationIntensity
public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 410f51045b9d..3c7692d03410 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1800,7 +1800,11 @@ public final class PermissionManager {
*/
@SystemApi
@NonNull
- @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+ })
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String persistentDeviceId) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index eea6464c9047..e26dc73f7172 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3583,6 +3583,12 @@ public final class Settings {
+ " and user:" + userHandle
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(name);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
@@ -3805,6 +3811,12 @@ public final class Settings {
+ " in package:" + cr.getPackageName()
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(prefix);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(prefix,
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
@@ -19992,6 +20004,13 @@ public final class Settings {
@Readable
public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
"consistent_notification_blocking_enabled";
+
+ /**
+ * Whether the Auto Bedtime Mode experience is enabled.
+ *
+ * @hide
+ */
+ public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af53a7f8..bd9ab86fa8d1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -468,6 +469,7 @@ public abstract class NotificationListenerService extends Service {
* object as well as its identifying information (tag and id) and source
* (package name).
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
@@ -481,6 +483,7 @@ public abstract class NotificationListenerService extends Service {
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications, including the newly posted one.
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
@@ -499,6 +502,7 @@ public abstract class NotificationListenerService extends Service {
* and source (package name) used to post the {@link android.app.Notification} that
* was just removed.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn) {
// optional
}
@@ -520,6 +524,7 @@ public abstract class NotificationListenerService extends Service {
* for active notifications.
*
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationRemoved(sbn);
}
@@ -541,6 +546,7 @@ public abstract class NotificationListenerService extends Service {
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
@NotificationCancelReason int reason) {
onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@ public abstract class NotificationListenerService extends Service {
*
* @hide
*/
+ @UiThread
@SystemApi
public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
@NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@ public abstract class NotificationListenerService extends Service {
* the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
*/
+ @UiThread
public void onListenerConnected() {
// optional
}
@@ -572,6 +580,7 @@ public abstract class NotificationListenerService extends Service {
* notification manager.You will not receive any events after this call, and may only
* call {@link #requestRebind(ComponentName)} at this time.
*/
+ @UiThread
public void onListenerDisconnected() {
// optional
}
@@ -582,6 +591,7 @@ public abstract class NotificationListenerService extends Service {
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRankingUpdate(RankingMap rankingMap) {
// optional
}
@@ -592,6 +602,7 @@ public abstract class NotificationListenerService extends Service {
*
* @param hints The current {@link #getCurrentListenerHints() listener hints}.
*/
+ @UiThread
public void onListenerHintsChanged(int hints) {
// optional
}
@@ -603,6 +614,7 @@ public abstract class NotificationListenerService extends Service {
* @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
* notifications
*/
+ @UiThread
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
// optional
}
@@ -620,6 +632,7 @@ public abstract class NotificationListenerService extends Service {
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelModified(String pkg, UserHandle user,
NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -638,6 +651,7 @@ public abstract class NotificationListenerService extends Service {
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelGroupModified(String pkg, UserHandle user,
NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -650,6 +664,7 @@ public abstract class NotificationListenerService extends Service {
* @param interruptionFilter The current
* {@link #getCurrentInterruptionFilter() interruption filter}.
*/
+ @UiThread
public void onInterruptionFilterChanged(int interruptionFilter) {
// optional
}
@@ -1197,6 +1212,11 @@ public abstract class NotificationListenerService extends Service {
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an
+ * {@link android.app.AutomaticZenRule} associated to the app.
+ *
* <p>The service should wait for the {@link #onListenerConnected()} event
* before performing this operation.
*
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index e44c69c4df28..6dbff7185f6f 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -36,11 +36,13 @@ import android.service.ondeviceintelligence.IRemoteProcessingService;
*/
oneway interface IOnDeviceIntelligenceService {
void getVersion(in RemoteCallback remoteCallback);
- void getFeature(in int featureId, in IFeatureCallback featureCallback);
- void listFeatures(in IListFeaturesCallback listFeaturesCallback);
- void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
- void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+ void requestFeatureDownload(int callerUid, in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+ void notifyInferenceServiceConnected();
+ void notifyInferenceServiceDisconnected();
} \ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index e3fda04e6592..73257ed7680a 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -18,7 +18,7 @@ package android.service.ondeviceintelligence;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
@@ -29,18 +29,18 @@ import android.service.ondeviceintelligence.IRemoteStorageService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
/**
- * Interface for a concrete implementation to provide on device trusted inference.
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
*
* @hide
*/
-oneway interface IOnDeviceTrustedInferenceService {
+oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
- in ITokenCountCallback tokenCountCallback);
- void processRequest(in Feature feature, in Content request, in int requestType,
+ void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ in ITokenInfoCallback tokenInfoCallback);
+ void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 46ba25d40bec..fce3689bb8b3 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -65,7 +65,7 @@ import java.util.function.LongConsumer;
/**
* Abstract base class for performing setup for on-device inference and providing file access to
- * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
*
* <p> A service that provides configuration and model files relevant to performing inference on
* device. The system's default OnDeviceIntelligenceService implementation is configured in
@@ -110,6 +110,8 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ // TODO(326052028) : Move the remote method calls to an app handler from the binder
+ // thread.
return new IOnDeviceIntelligenceService.Stub() {
/** {@inheritDoc} */
@Override
@@ -123,38 +125,40 @@ public abstract class OnDeviceIntelligenceService extends Service {
}
@Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+ public void listFeatures(int callerUid,
+ IListFeaturesCallback listFeaturesCallback) {
Objects.requireNonNull(listFeaturesCallback);
- OnDeviceIntelligenceService.this.onListFeatures(
+ OnDeviceIntelligenceService.this.onListFeatures(callerUid,
wrapListFeaturesCallback(listFeaturesCallback));
}
@Override
- public void getFeature(int id, IFeatureCallback featureCallback) {
+ public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
Objects.requireNonNull(featureCallback);
- OnDeviceIntelligenceService.this.onGetFeature(id,
- wrapFeatureCallback(featureCallback));
+ OnDeviceIntelligenceService.this.onGetFeature(callerUid,
+ id, wrapFeatureCallback(featureCallback));
}
@Override
- public void getFeatureDetails(Feature feature,
+ public void getFeatureDetails(int callerUid, Feature feature,
IFeatureDetailsCallback featureDetailsCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(featureDetailsCallback);
- OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
- wrapFeatureDetailsCallback(featureDetailsCallback));
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid,
+ feature, wrapFeatureDetailsCallback(featureDetailsCallback));
}
@Override
- public void requestFeatureDownload(Feature feature,
+ public void requestFeatureDownload(int callerUid, Feature feature,
ICancellationSignal cancellationSignal,
IDownloadCallback downloadCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(downloadCallback);
- OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+ OnDeviceIntelligenceService.this.onDownloadFeature(callerUid,
+ feature,
CancellationSignal.fromTransport(cancellationSignal),
wrapDownloadCallback(downloadCallback));
}
@@ -188,12 +192,38 @@ public abstract class OnDeviceIntelligenceService extends Service {
IRemoteProcessingService remoteProcessingService) {
mRemoteProcessingService = remoteProcessingService;
}
+
+ @Override
+ public void notifyInferenceServiceConnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+ }
+
+ @Override
+ public void notifyInferenceServiceDisconnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+ }
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
return null;
}
+
+ /**
+ * Invoked when a new instance of the remote inference service is created.
+ * This method should be used as a signal to perform any initialization operations, for e.g. by
+ * invoking the {@link #updateProcessingState} method to initialize the remote processing
+ * service.
+ */
+ public abstract void onInferenceServiceConnected();
+
+
+ /**
+ * Invoked when an instance of the remote inference service is disconnected.
+ */
+ public abstract void onInferenceServiceDisconnected();
+
+
/**
* Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
* service if there is a state change to be performed.
@@ -391,6 +421,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
* Request download for feature that is requested and listen to download progress updates. If
* the download completes successfully, success callback should be populated.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature the feature for which files need to be downlaoded.
* process.
* @param cancellationSignal signal to attach a listener to, and receive cancellation signals
@@ -398,7 +429,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
* @param downloadCallback callback to populate download updates for clients to listen on..
*/
public abstract void onDownloadFeature(
- @NonNull Feature feature,
+ int callerUid, @NonNull Feature feature,
@Nullable CancellationSignal cancellationSignal,
@NonNull DownloadCallback downloadCallback);
@@ -407,20 +438,22 @@ public abstract class OnDeviceIntelligenceService extends Service {
* implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
* details the client is looking for.
*
- * @param feature the feature for which status needs to be known.
- * @param featureStatusCallback callback to populate the resulting feature status.
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature the feature for which status needs to be known.
+ * @param featureDetailsCallback callback to populate the resulting feature status.
*/
- public abstract void onGetFeatureDetails(@NonNull Feature feature,
+ public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
/**
* Get feature using the provided identifier to the remote implementation.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param featureCallback callback to populate the features list.
*/
- public abstract void onGetFeature(int featureId,
+ public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
@@ -428,9 +461,10 @@ public abstract class OnDeviceIntelligenceService extends Service {
* List all features which are available in the remote implementation. The implementation might
* choose to provide only a certain list of features based on the caller.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param listFeaturesCallback callback to populate the features list.
*/
- public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+ public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
/**
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 86001975cc09..7f7f9c28c60e 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
package android.service.ondeviceintelligence;
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
import android.annotation.CallbackExecutor;
@@ -23,6 +24,7 @@ import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ondeviceintelligence.Content;
@@ -30,14 +32,18 @@ import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,8 +81,8 @@ import java.util.function.Consumer;
*
* <pre>
* {@literal
- * <service android:name=".SampleTrustedInferenceService"
- * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * <service android:name=".SampleSandboxedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
* android:isolatedProcess="true">
* </service>}
* </pre>
@@ -85,18 +91,18 @@ import java.util.function.Consumer;
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public abstract class OnDeviceTrustedInferenceService extends Service {
- private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+ private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the
- * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
* permission so that other applications can not abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
private IRemoteStorageService mRemoteStorageService;
@@ -107,7 +113,7 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return new IOnDeviceTrustedInferenceService.Stub() {
+ return new IOnDeviceSandboxedInferenceService.Stub() {
@Override
public void registerRemoteStorageService(IRemoteStorageService storageService) {
Objects.requireNonNull(storageService);
@@ -115,50 +121,48 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
}
@Override
- public void requestTokenCount(Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Content request,
ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountCallback) {
+ ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(tokenCountCallback);
- OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+ Objects.requireNonNull(tokenInfoCallback);
+ OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid,
+ feature,
request,
CancellationSignal.fromTransport(cancellationSignal),
- wrapTokenCountCallback(tokenCountCallback));
+ wrapTokenInfoCallback(tokenInfoCallback));
}
@Override
- public void processRequestStreaming(Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+ OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid,
+ feature,
request,
requestType,
CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapStreamingResponseCallback(callback)
- );
+ wrapStreamingResponseCallback(callback));
}
@Override
- public void processRequest(Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
-
- OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
- requestType, CancellationSignal.fromTransport(cancellationSignal),
+ OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature,
+ request, requestType,
+ CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapResponseCallback(callback)
- );
+ wrapResponseCallback(callback));
}
@Override
@@ -167,7 +171,7 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
Objects.requireNonNull(processingState);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState,
+ OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState,
wrapOutcomeReceiver(callback)
);
}
@@ -178,35 +182,37 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
}
/**
- * Invoked when caller wants to obtain a count of number of tokens present in the passed in
- * Request associated with the provided feature.
+ * Invoked when caller wants to obtain token info related to the payload in the passed
+ * content, associated with the provided feature.
* The expectation from the implementation is that when processing is complete, it
- * should provide the token count in the {@link OutcomeReceiver#onResult}.
+ * should provide the token info in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param cancellationSignal Cancellation Signal to receive cancellation events from client and
* configure a listener to.
- * @param callback callback to populate failure and full response for the provided
+ * @param callback callback to populate failure or the token info for the provided
* request.
*/
@NonNull
- public abstract void onCountTokens(
- @NonNull Feature feature,
+ public abstract void onTokenInfoRequest(
+ int callerUid, @NonNull Feature feature,
@NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+ * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
* provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+ * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
* processing completion.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -218,13 +224,12 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
*/
@NonNull
public abstract void onProcessRequestStreaming(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull StreamedProcessingOutcomeReceiver callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -233,6 +238,7 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
* should
* provide the complete response in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -244,13 +250,12 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
*/
@NonNull
public abstract void onProcessRequest(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull ProcessingOutcomeReceiver callback);
/**
@@ -335,6 +340,26 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
}
}
+
+ /**
+ * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+ * implementation. For e.g. see
+ * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * Consumer)} where we use the executor to populate the consumer.
+ * <p>
+ * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+ * provide the executor you want to use for incoming IPC.
+ *
+ * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+ * to {@link OnDeviceSandboxedInferenceService}.
+ */
+ @SuppressLint("OnNameExpected")
+ @NonNull
+ public Executor getCallbackExecutor() {
+ return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+ }
+
+
private RemoteCallback wrapResultReceiverAsReadOnly(
@NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
@NonNull Executor executor) {
@@ -355,10 +380,9 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
});
}
- private OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+ private ProcessingOutcomeReceiver wrapResponseCallback(
IResponseCallback callback) {
- return new OutcomeReceiver<>() {
+ return new ProcessingOutcomeReceiver() {
@Override
public void onResult(@androidx.annotation.NonNull Content response) {
try {
@@ -378,13 +402,23 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+ private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamingResponseReceiver<>() {
+ return new StreamedProcessingOutcomeReceiver() {
@Override
public void onNewContent(@androidx.annotation.NonNull Content content) {
try {
@@ -413,17 +447,43 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private OutcomeReceiver<Long,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
- ITokenCountCallback tokenCountCallback) {
+ private RemoteCallback wrapRemoteCallback(
+ @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ return new RemoteCallback(
+ result -> {
+ if (result != null) {
+ getCallbackExecutor().execute(() -> contentCallback.accept(
+ result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ Content.class)));
+ } else {
+ getCallbackExecutor().execute(
+ () -> contentCallback.accept(null));
+ }
+ });
+ }
+
+ private OutcomeReceiver<TokenInfo,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
- public void onResult(Long tokenCount) {
+ public void onResult(TokenInfo tokenInfo) {
try {
- tokenCountCallback.onSuccess(tokenCount);
+ tokenInfoCallback.onSuccess(tokenInfo);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -433,7 +493,7 @@ public abstract class OnDeviceTrustedInferenceService extends Service {
public void onError(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
try {
- tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
} catch (RemoteException e) {
Slog.e(TAG, "Error sending failure: " + e);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 67a397828fa0..0ae3e598f8b9 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1775,9 +1775,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
@CriticalNative
private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
@CriticalNative
- private static native float nativeGetXOffset(long nativePtr);
+ private static native float nativeGetRawXOffset(long nativePtr);
@CriticalNative
- private static native float nativeGetYOffset(long nativePtr);
+ private static native float nativeGetRawYOffset(long nativePtr);
@CriticalNative
private static native float nativeGetXPrecision(long nativePtr);
@CriticalNative
@@ -3745,7 +3745,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetRawXOffset(mNativePtr), nativeGetRawYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr),
nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d29963c6a82b..a6f380d4d483 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33760,7 +33760,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@FlaggedApi(FLAG_VIEW_VELOCITY_API)
public float getFrameContentVelocity() {
if (viewVelocityApi()) {
- return mFrameContentVelocity;
+ return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
}
return 0;
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 131fca7d923a..1efd37591ee4 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -315,7 +315,8 @@ public abstract class ViewStructure {
/**
* Add to this view's child count. This increases the current child count by
* <var>num</var> children beyond what was last set by {@link #setChildCount}
- * or {@link #addChildCount}. The index at which the new child starts in the child
+ * or {@link #addChildCount}. The index at which the new
+ * child starts in the child
* array is returned.
*
* @param num The number of new children to add.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe4ac4e2f0f1..a2d8d80096c5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -4299,7 +4299,7 @@ public class RemoteViews implements Parcelable, Filter {
}
if (mode == MODE_NORMAL) {
- mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mViewId = parcel.readInt();
@@ -6805,7 +6805,7 @@ public class RemoteViews implements Parcelable, Filter {
mBitmapCache.writeBitmapsToParcel(dest, flags);
mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
- mApplication.writeToParcel(dest, flags);
+ dest.writeTypedObject(mApplication, flags);
if (mIsRoot || mIdealSize == null) {
dest.writeInt(0);
} else {
@@ -6893,7 +6893,8 @@ public class RemoteViews implements Parcelable, Filter {
* @hide
*/
public boolean hasSameAppInfo(ApplicationInfo info) {
- return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ return mApplication == null || mApplication.packageName.equals(info.packageName)
+ && mApplication.uid == info.uid;
}
/**
@@ -7672,8 +7673,7 @@ public class RemoteViews implements Parcelable, Filter {
byte[] instruction;
final List<byte[]> instructions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
- instruction = new byte[in.readInt()];
- in.readByteArray(instruction);
+ instruction = in.readBlob();
instructions.add(instruction);
}
return new DrawInstructions(instructions);
@@ -7688,8 +7688,7 @@ public class RemoteViews implements Parcelable, Filter {
final List<byte[]> instructions = drawInstructions.mInstructions;
dest.writeInt(instructions.size());
for (byte[] instruction : instructions) {
- dest.writeInt(instruction.length);
- dest.writeByteArray(instruction);
+ dest.writeBlob(instruction);
}
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 85bdbb908205..e55cdef82235 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,11 +80,10 @@ interface IAppWidgetService {
in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
- void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+ boolean setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
in RemoteViews preview);
@nullable RemoteViews getWidgetPreview(in String callingPackage,
in ComponentName providerComponent, in int profileId, in int widgetCategory);
void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
-
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index bd806bfb3e24..91678c793b44 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -560,6 +560,19 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
+ /*
+ * (long) The reset interval for generated preview API calls.
+ */
+ public static final String GENERATED_PREVIEW_API_RESET_INTERVAL_MS =
+ "generated_preview_api_reset_interval_ms";
+
+ /*
+ * (int) The max number of generated preview API calls per reset interval.
+ */
+ public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
+ "generated_preview_api_max_calls_per_interval";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 071f933db065..83b6afa8f8f6 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -428,7 +428,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
return JNI_ERR;
}
- if (!systemProperties["register_properties_during_load"].empty()) {
+ if (!systemProperties["icu.data.path"].empty()) {
// Set the location of ICU data
bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
if (!icuInitialized) {
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 23adb8f7e2a3..1a86363b6ed9 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -411,8 +411,8 @@ static void android_view_MotionEvent_nativeAddBatch(JNIEnv* env, jclass clazz,
jniThrowNullPointerException(env, "pointerCoords");
return;
}
- pointerCoordsToNative(env, pointerCoordsObj,
- event->getXOffset(), event->getYOffset(), &rawPointerCoords[i]);
+ pointerCoordsToNative(env, pointerCoordsObj, event->getRawXOffset(), event->getRawYOffset(),
+ &rawPointerCoords[i]);
env->DeleteLocalRef(pointerCoordsObj);
}
@@ -735,14 +735,14 @@ static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloa
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getXOffset();
+ return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getYOffset();
+ return event->getRawYOffset();
}
static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
@@ -871,8 +871,8 @@ static const JNINativeMethod gMotionEventMethods[] = {
{"nativeGetClassification", "(J)I",
(void*)android_view_MotionEvent_nativeGetClassification},
{"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation},
- {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset},
- {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset},
+ {"nativeGetRawXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawXOffset},
+ {"nativeGetRawYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawYOffset},
{"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision},
{"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision},
{"nativeGetXCursorPosition", "(J)F",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 52bad21e156d..1acdc75a600e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6772,13 +6772,6 @@
<permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
android:protectionLevel="signature" />
- <!-- Allows privileged apps to access the background face authentication.
- @SystemApi
- @FlaggedApi("android.hardware.biometrics.face_background_authentication")
- @hide -->
- <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
android:protectionLevel="signature" />
@@ -7955,12 +7948,12 @@
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an app to bind the on-device trusted service.
+ <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
<p>Protection level: signature|privileged
@hide
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
-->
- <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"
+ <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
android:protectionLevel="signature"/>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index df8158dbb6c8..6e804c0b428a 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -146,7 +146,7 @@ a similar way.
<item name="windowAnimationStyle">@style/Animation.InputMethod</item>
<item name="imeFullscreenBackground">?colorBackground</item>
<item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
- <item name="windowSwipeToDismiss">false</item>
+ <item name="windowSwipeToDismiss">true</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4f20fceef8d1..9d902c94ffe4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4673,8 +4673,8 @@
<!-- The component name for the default system on-device intelligence service, -->
<string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
- <!-- The component name for the default system on-device trusted inference service. -->
- <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+ <!-- The component name for the default system on-device sandboxed inference service. -->
+ <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb83f1c..238772f5961e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6434,4 +6434,6 @@ ul.</string>
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
<string name="satellite_notification_how_it_works">How it works</string>
+ <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+ <string name="unarchival_session_app_label">Pending...</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 150951fd57f8..4b716543c726 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3916,7 +3916,7 @@
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
<java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
- <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
+ <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
@@ -5373,4 +5373,6 @@
<java-symbol type="string" name="config_defaultContextualSearchKey" />
<java-symbol type="string" name="config_defaultContextualSearchEnabled" />
<java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+ <java-symbol type="string" name="unarchival_session_app_label" />
</resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 15bb66b3e303..8d9fad999624 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -90,7 +90,7 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase {
private static final long TEST_HD_FREQUENCY_VALUE = 95_300;
private static final long TEST_HD_STATION_ID_EXT_VALUE = 0x100000001L
| (TEST_HD_FREQUENCY_VALUE << 36);
- private static final long TEST_HD_LOCATION_VALUE = 0x89CC8E06CCB9ECL;
+ private static final long TEST_HD_LOCATION_VALUE = 0x4E647007665CF6L;
private static final long TEST_VENDOR_ID_VALUE = 9_901;
private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 37a499adf682..6cc54850dce6 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -110,8 +110,6 @@ public class BugreportManagerTest {
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
- Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
};
private Handler mHandler;
@@ -257,6 +255,38 @@ public class BugreportManagerTest {
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
}
+ @LargeTest
+ @Test
+ public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+
+ // Simulate lost of pre-dumped data.
+ // For example it can happen in this scenario:
+ // 1. Pre-dump data
+ // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+ // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+ removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+ // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+ // the flag will be ignored and data will be dumped as in normal flow.
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -506,9 +536,6 @@ public class BugreportManagerTest {
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"cmd window tracing start"
);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "service call SurfaceFlinger 1025 i32 1"
- );
}
private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@ public class BugreportManagerTest {
return files;
}
+ private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "rm -f " + path.toString()
+ );
+ }
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 34f5841aef72..3a872b50af75 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,7 +18,6 @@ package android.hardware.face;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
import static com.google.common.truth.Truth.assertThat;
@@ -36,15 +35,12 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
-import android.hardware.biometrics.BiometricPrompt;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import com.android.internal.R;
@@ -62,7 +58,6 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -83,8 +78,6 @@ public class FaceManagerTest {
@Mock
private FaceManager.AuthenticationCallback mAuthCallback;
@Mock
- private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
- @Mock
private FaceManager.EnrollmentCallback mEnrollmentCallback;
@Mock
private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -98,16 +91,13 @@ public class FaceManagerTest {
private TestLooper mLooper;
private Handler mHandler;
private FaceManager mFaceManager;
- private Executor mExecutor;
@Before
public void setUp() throws Exception {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mExecutor = new HandlerExecutor(mHandler);
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
- when(mContext.getMainExecutor()).thenReturn(mExecutor);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -169,19 +159,6 @@ public class FaceManagerTest {
}
@Test
- @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- public void authenticateInBackground_errorWhenUnavailable() throws Exception {
- when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
- .thenThrow(new RemoteException());
-
- mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
- mBioAuthCallback);
- mLooper.dispatchAll();
-
- verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
- }
-
- @Test
public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
index 128c54b77e57..d437f7bc4060 100644
--- a/core/tests/coretests/src/android/view/ViewVelocityTest.java
+++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -23,6 +25,7 @@ import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -64,6 +67,7 @@ public class ViewVelocityTest {
@UiThreadTest
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void frameRateChangesWhenContentMoves() {
mMovingView.offsetLeftAndRight(100);
float frameRate = mViewRoot.getPreferredFrameRate();
@@ -72,11 +76,13 @@ public class ViewVelocityTest {
@UiThreadTest
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void firstFrameNoMovement() {
assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
}
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void touchBoostDisable() throws Throwable {
mActivityRule.runOnUiThread(() -> {
long now = SystemClock.uptimeMillis();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 051e73f4d4c8..0f12438613cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -455,7 +455,7 @@ applications that come with the platform
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
<permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index b6ce9b64323b..f9ac02a0055a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -789,10 +789,8 @@ public class Canvas extends BaseCanvas {
* @param m The 4x4 matrix to preconcatenate with the current matrix
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void concat44(@Nullable Matrix44 m) {
- if (m != null) {
- nConcat(mNativeCanvasWrapper, m.mBackingArray);
- }
+ public void concat(@Nullable Matrix44 m) {
+ if (m != null) nConcat(mNativeCanvasWrapper, m.mBackingArray);
}
/**
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7a6728..a99e20101c3b 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@ public class Matrix44 {
/**
* Gets the value at the matrix's row and column.
*
- * @param row An integer from 0 to 4 indicating the row of the value to get
- * @param col An integer from 0 to 4 indicating the column of the value to get
+ * @param row An integer from 0 to 3 indicating the row of the value to get
+ * @param col An integer from 0 to 3 indicating the column of the value to get
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public float get(int row, int col) {
+ public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
return mBackingArray[row * 4 + col];
}
@@ -112,12 +113,13 @@ public class Matrix44 {
/**
* Sets the value at the matrix's row and column to the provided value.
*
- * @param row An integer from 0 to 4 indicating the row of the value to change
- * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param row An integer from 0 to 3 indicating the row of the value to change
+ * @param col An integer from 0 to 3 indicating the column of the value to change
* @param val The value the element at the specified index will be set to
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void set(int row, int col, float val) {
+ public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+ float val) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
mBackingArray[row * 4 + col] = val;
} else {
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 948264579e1d..294b1f0e21fd 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -25,7 +25,7 @@
<shape
android:shape="ring"
android:thickness="3dp"
- android:innerRadius="17dp"
+ android:innerRadius="14dp"
android:useLevel="true">
</shape>
</rotate>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index 02b707568cd0..e5fe1b5431eb 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
+ android:width="24dp"
+ android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
- android:fillColor="@android:color/white"
- android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+ android:fillColor="@android:color/black"
+ android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
deleted file mode 100644
index 7c4f49979455..000000000000
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20dp"
- android:height="20dp"
- android:viewportWidth="20"
- android:viewportHeight="20">
- <path
- android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
- android:fillColor="#1C1C14"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index a5605a7ff50a..fa18e2bc5add 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -80,11 +80,14 @@
<com.android.wm.shell.windowdecor.MaximizeButtonView
android:id="@+id/maximize_button_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="44dp"
+ android:layout_height="40dp"
android:layout_gravity="end"
+ android:layout_marginHorizontal="8dp"
+ android:paddingHorizontal="5dp"
+ android:paddingVertical="3dp"
android:clickable="true"
- android:focusable="true" />
+ android:focusable="true"/>
<ImageButton
android:id="@+id/close_window"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index c6f85a0b4ed4..fca2fe4eddc5 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -134,15 +134,6 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
- android:drawableTint="?androidprv:attr/materialColorOnSurface"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index e0057fe64fd2..296c89568386 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -20,16 +20,16 @@
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:progressDrawable="@drawable/circular_progress"
- android:layout_width="40dp"
- android:layout_height="40dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
android:indeterminate="false"
android:visibility="invisible"/>
<ImageButton
android:id="@+id/maximize_window"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:padding="9dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
+ android:padding="5dp"
android:contentDescription="@string/maximize_button_text"
android:tint="?androidprv:attr/materialColorOnSurface"
android:background="?android:selectableItemBackgroundBorderless"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4d47ca998d8b..139cde2c66f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -983,7 +983,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// cache current min/max size
Point minSize = mPipBoundsState.getMinSize();
Point maxSize = mPipBoundsState.getMaxSize();
- mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
+ final float aspectRatioFloat;
+ if (pictureInPictureParams.hasSetAspectRatio()) {
+ aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+ } else {
+ aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+ }
+ mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
// restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f4ccd689f938..500e6f4d6369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -442,12 +442,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
- } else if (id == R.id.select_button) {
- if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
- // TODO(b/278084491): dev option to enable display switching
- // remove when select is implemented
- mDesktopTasksController.moveToNextDisplay(mTaskId);
- }
} else if (id == R.id.maximize_window) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
decoration.closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index b37dd0d6fd2d..3d0dd31cc686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -35,7 +35,6 @@ import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -53,6 +52,7 @@ import com.android.wm.shell.R;
*/
class HandleMenu {
private static final String TAG = "HandleMenu";
+ private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
private final Context mContext;
private final WindowDecoration mParentDecor;
private WindowDecoration.AdditionalWindow mHandleMenuWindow;
@@ -185,11 +185,9 @@ class HandleMenu {
* Set up interactive elements & height of handle menu's more actions pill
*/
private void setupMoreActionsPill(View handleMenu) {
- final Button selectBtn = handleMenu.findViewById(R.id.select_button);
- selectBtn.setOnClickListener(mOnClickListener);
- final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
- // TODO: Remove once implemented.
- screenshotBtn.setVisibility(View.GONE);
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
+ }
}
/**
@@ -305,12 +303,15 @@ class HandleMenu {
* Determines handle menu height based on if windowing pill should be shown.
*/
private int getHandleMenuHeight(Resources resources) {
- int menuHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_height);
+ int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
if (!mShouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_windowing_pill_height);
}
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+ }
return menuHeight;
}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
new file mode 100644
index 000000000000..4b008a7b4815
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
@@ -0,0 +1,51 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+cc_fuzz {
+ name: "resxmlparser_fuzzer",
+ srcs: [
+ "resxmlparser_fuzzer.cpp",
+ ],
+ host_supported: true,
+
+ static_libs: ["libgmock"],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ darwin: {
+ // libbinder is not supported on mac
+ enabled: false,
+ },
+ },
+
+ include_dirs: [
+ "system/incremental_delivery/incfs/util/include/",
+ ],
+
+ corpus: ["testdata/*"],
+ dictionary: "xmlparser_fuzzer.dict",
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
new file mode 100644
index 000000000000..829a39617012
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+#include <cstdint>
+#include <cstddef>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "androidfw/ResourceTypes.h"
+
+static void populateDynamicRefTableWithFuzzedData(
+ android::DynamicRefTable& table,
+ FuzzedDataProvider& fuzzedDataProvider) {
+
+ const size_t numMappings = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 5);
+ for (size_t i = 0; i < numMappings; ++i) {
+ const uint8_t packageId = fuzzedDataProvider.ConsumeIntegralInRange<uint8_t>(0x02, 0x7F);
+
+ // Generate a package name
+ std::string packageName;
+ size_t packageNameLength = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 128);
+ for (size_t j = 0; j < packageNameLength; ++j) {
+ // Consume characters only in the ASCII range (0x20 to 0x7E) to ensure valid UTF-8
+ char ch = fuzzedDataProvider.ConsumeIntegralInRange<char>(0x20, 0x7E);
+ packageName.push_back(ch);
+ }
+
+ // Convert std::string to String16 for compatibility
+ android::String16 androidPackageName(packageName.c_str(), packageName.length());
+
+ // Add the mapping to the table
+ table.addMapping(androidPackageName, packageId);
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ FuzzedDataProvider fuzzedDataProvider(data, size);
+
+ auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+
+ // Populate the DynamicRefTable with fuzzed data
+ populateDynamicRefTableWithFuzzedData(*dynamic_ref_table, fuzzedDataProvider);
+
+ auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
+
+ std::vector<uint8_t> xmlData = fuzzedDataProvider.ConsumeRemainingBytes<uint8_t>();
+ if (tree.setTo(xmlData.data(), xmlData.size()) != android::NO_ERROR) {
+ return 0; // Exit early if unable to parse XML data
+ }
+
+ tree.restart();
+
+ size_t len = 0;
+ auto code = tree.next();
+ if (code == android::ResXMLParser::START_TAG) {
+ // Access element name
+ auto name = tree.getElementName(&len);
+
+ // Access attributes of the current element
+ for (size_t i = 0; i < tree.getAttributeCount(); i++) {
+ // Access attribute name
+ auto attrName = tree.getAttributeName(i, &len);
+ }
+ } else if (code == android::ResXMLParser::TEXT) {
+ const auto text = tree.getText(&len);
+ }
+ return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
new file mode 100644
index 000000000000..417fec72be6a
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child id="1">
+ <subchild type="A">Content A</subchild>
+ <subchild type="B">Content B</subchild>
+ </child>
+ <child id="2" extra="data">
+ <subchild type="C">Content C</subchild>
+ </child>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
new file mode 100644
index 000000000000..7e13db536fc9
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child1>Value 1</child1>
+ <child2>Value 2</child2>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
new file mode 100644
index 000000000000..90cdf3513be9
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <!-- Example with special characters and CDATA -->
+ <data><![CDATA[Some <encoded> data & other "special" characters]]></data>
+ <message>Hello &amp; Welcome!</message>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
new file mode 100644
index 000000000000..745ded4810f3
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
@@ -0,0 +1,11 @@
+root_tag=<root>
+child_tag=<child>
+end_child_tag=</child>
+id_attr=id="
+type_attr=type="
+cdata_start=<![CDATA[
+cdata_end=]]>
+ampersand_entity=&amp;
+xml_header=<?xml version="1.0" encoding="UTF-8"?>
+comment_start=<!--
+comment_end= -->
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
index 8f227b1604b7..89d14fb982d9 100644
--- a/location/java/android/location/provider/ForwardGeocodeRequest.java
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -260,7 +260,11 @@ public final class ForwardGeocodeRequest implements Parcelable {
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
index 57c9047f07ca..210770740fcc 100644
--- a/location/java/android/location/provider/ReverseGeocodeRequest.java
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -207,7 +207,11 @@ public final class ReverseGeocodeRequest implements Parcelable {
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 53699bc706ea..0a223142954f 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -124,11 +124,11 @@ int64_t AMotionEvent_getEventTime(const AInputEvent* motion_event) {
}
float AMotionEvent_getXOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getXOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawXOffset();
}
float AMotionEvent_getYOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getYOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawYOffset();
}
float AMotionEvent_getXPrecision(const AInputEvent* motion_event) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index b6bc40d5fc19..7dd16ba6c18e 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -38,6 +38,7 @@ java_sdk_library {
name: "framework-nfc",
libs: [
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+ "framework-permission-s",
],
static_libs: [
"android.nfc.flags-aconfig-java",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index c0db089253e7..54f1421e463d 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@ package android.nfc {
package android.nfc.cardemulation {
public final class CardEmulation {
- method public boolean categoryAllowsForegroundPreference(String);
+ method @Deprecated public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method public int getSelectionModeForCategory(String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method @Deprecated public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index ea58504063d7..e55f5403ed83 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,6 +27,7 @@ import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -278,13 +279,22 @@ public final class CardEmulation {
* @param category The category, e.g. {@link #CATEGORY_PAYMENT}
* @return whether AIDs in the category can be handled by a service
* specified by the foreground app.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
@SuppressWarnings("NonUserGetterCalled")
+ @Deprecated
public boolean categoryAllowsForegroundPreference(String category) {
+ Context contextAsUser = mContext.createContextAsUser(
+ UserHandle.of(UserHandle.myUserId()), 0);
+ RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ return true;
+ }
if (CATEGORY_PAYMENT.equals(category)) {
boolean preferForeground = false;
- Context contextAsUser = mContext.createContextAsUser(
- UserHandle.of(UserHandle.myUserId()), 0);
try {
preferForeground = Settings.Secure.getInt(
contextAsUser.getContentResolver(),
@@ -309,7 +319,12 @@ public final class CardEmulation {
* to pick a service if there is a conflict.
* @param category The category, for example {@link #CATEGORY_PAYMENT}
* @return the selection mode for the passed in category
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
+ @Deprecated
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
boolean paymentRegistered = false;
@@ -792,8 +807,15 @@ public final class CardEmulation {
* (e.g. eSE/eSE1, eSE2, etc.).
* 2. "OffHost" if the payment service does not specify secure element
* name.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
try {
@@ -836,8 +858,15 @@ public final class CardEmulation {
* Returns a user-visible description of the preferred payment service.
*
* @return the preferred payment service description
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
try {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4e1f4ee2e565..d7a2e3624eab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -21,14 +21,13 @@ import android.app.assist.AssistStructure
import android.content.Context
import android.credentials.CredentialManager
import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialException
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCandidateCredentialsException
import android.credentials.CredentialOption
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
@@ -123,13 +122,10 @@ class CredentialAutofillService : AutofillService() {
// TODO(b/324635774): Use callback for validating. If the request is coming
// directly from the view, there should be a corresponding callback, otherwise
// we should fail fast,
- val getCredCallback = getCredManCallback(structure)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
return
- } else if (getCredCallback == null) {
- Log.i(TAG, "No credential manager callback found")
}
val credentialManager: CredentialManager =
getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
@@ -353,6 +349,7 @@ class CredentialAutofillService : AutofillService() {
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
.setTitle(displayName)
+ icon.setTintBlendMode(BlendMode.DST)
sliceBuilder.setStartIcon(icon)
if (primaryEntry.credentialType ==
CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
@@ -526,42 +523,6 @@ class CredentialAutofillService : AutofillService() {
TODO("Not yet implemented")
}
- private fun getCredManCallback(structure: AssistStructure): OutcomeReceiver<
- GetCredentialResponse, GetCredentialException>? {
- return traverseStructureForCallback(structure)
- }
-
- private fun traverseStructureForCallback(
- structure: AssistStructure
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val windowNodes: List<AssistStructure.WindowNode> =
- structure.run {
- (0 until windowNodeCount).map { getWindowNodeAt(it) }
- }
-
- windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- return traverseNodeForCallback(windowNode.rootViewNode)
- }
- return null
- }
-
- private fun traverseNodeForCallback(
- viewNode: AssistStructure.ViewNode
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val children: List<AssistStructure.ViewNode> =
- viewNode.run {
- (0 until childCount).map { getChildAt(it) }
- }
-
- children.forEach { childNode: AssistStructure.ViewNode ->
- if (childNode.isFocused() && childNode.credentialManagerCallback != null) {
- return childNode.credentialManagerCallback
- }
- return traverseNodeForCallback(childNode)
- }
- return null
- }
-
private fun getCredManRequest(
structure: AssistStructure,
sessionId: Int,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index a7b5c36215cf..e43b09ede1cb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -39,7 +39,6 @@ import com.android.credentialmanager.common.material.rememberModalBottomSheetSta
import com.android.credentialmanager.ui.theme.EntryShape
import kotlinx.coroutines.launch
-
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -73,7 +72,7 @@ fun ModalBottomSheet(
dragHandle = null,
// Never take over the full screen. We always want to leave some top scrim space
// for exiting and viewing the underlying app to help a user gain context.
- modifier = Modifier.padding(top = 56.dp),
+ modifier = Modifier.padding(top = 72.dp),
)
} else {
val scope = rememberCoroutineScope()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index c68ae8b168fb..006a2d9858c4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.common.ui
+import android.credentials.flags.Flags
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
@@ -63,7 +64,7 @@ fun SheetContainerCard(
modifier = Modifier.padding(
start = 24.dp,
end = 24.dp,
- bottom = 18.dp,
+ bottom = if (Flags.selectorUiImprovementsEnabled()) 8.dp else 18.dp,
top = if (topAppBar == null) 24.dp else 0.dp
).fillMaxWidth().wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd204b640..ff421bc47511 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@ package com.android.credentialmanager.common.ui
import android.content.Context
import android.util.Size
import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
class InlinePresentationsFactory {
companion object {
- private const val googleSansMediumFontFamily = "google-sans-medium"
- private const val googleSansTextFontFamily = "google-sans-text"
- // There is no min width required for now but this is needed for the spec builder
- private const val minInlineWidth = 5000
+ // There is no max width required for now but this is needed for the spec builder
+ private const val maxInlineWidth = 5000
fun modifyInlinePresentationSpec(context: Context,
originalSpec: InlinePresentationSpec): InlinePresentationSpec {
return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
.minSize.height),
- Size(minInlineWidth, originalSpec
+ Size(maxInlineWidth, originalSpec
.maxSize.height))
- .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
- .build()
- }
-
-
- fun getStyle(context: Context): Style {
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- val textColorSecondary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_secondary)
- val textColorBackground = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.inline_background)
- val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.horizontal_chip_padding)
- val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.vertical_chip_padding)
- return InlineSuggestionUi.newStyleBuilder()
- .setChipStyle(
- ViewStyle.Builder().setPadding(chipHorizontalPadding,
- chipVerticalPadding,
- chipHorizontalPadding, chipVerticalPadding).build()
- )
- .setTitleStyle(
- TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
- (TypedValue.COMPLEX_UNIT_DIP, 14F)
- .setTypeface(googleSansMediumFontFamily,
- Typeface.NORMAL).setBackgroundColor(textColorBackground)
- .build()
- )
- .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
- .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
- (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
- (textColorBackground).build())
+ .setStyle(originalSpec.getStyle())
.build()
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 33086f98efa0..e489bc552b25 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1918,16 +1918,6 @@ public class ApplicationsState {
}
};
- public static final AppFilter FILTER_PERSONAL_OR_PRIVATE = new AppFilter() {
- @Override
- public void init() {}
-
- @Override
- public boolean filterApp(AppEntry entry) {
- return entry.showInPersonalTab || entry.isPrivateProfile();
- }
- };
-
/**
* Displays a combined list with "downloaded" and "visible in launcher" apps only.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index da1fd55c4ccb..0c7d6f093289 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -311,7 +311,7 @@ object BluetoothLeBroadcastMetadataExt {
}
builder.apply {
- setSourceDevice(device, sourceAddrType)
+ setSourceDevice(device, addrType)
setSourceAdvertisingSid(sourceAdvertiserSid)
setBroadcastId(broadcastId)
setBroadcastName(broadcastName)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 5f026c451152..e34c50eb5ce6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,7 +44,6 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
@@ -67,6 +66,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.flags.Flags;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -81,11 +81,43 @@ import java.util.stream.Stream;
/** InfoMediaManager provide interface to get InfoMediaDevice list. */
@RequiresApi(Build.VERSION_CODES.R)
-public abstract class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager {
+ /** Callback for notifying device is added, removed and attributes changed. */
+ public interface MediaDeviceCallback {
- private static final String TAG = "InfoMediaManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ /**
+ * Callback for notifying MediaDevice list is added.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListAdded(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying MediaDevice list is removed.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListRemoved(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying connected MediaDevice is changed.
+ *
+ * @param id the id of MediaDevice
+ */
+ void onConnectedDeviceChanged(@Nullable String id);
+
+ /**
+ * Callback for notifying that transferring is failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings: {@link
+ * android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_REJECTED}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ void onRequestFailed(int reason);
+ }
/** Checked exception that signals the specified package is not present in the system. */
public static class PackageNotAvailableException extends Exception {
@@ -94,19 +126,22 @@ public abstract class InfoMediaManager extends MediaManager {
}
}
+ private static final String TAG = "InfoMediaManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ @NonNull protected final Context mContext;
@NonNull protected final String mPackageName;
+ private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
/* package */ InfoMediaManager(
- Context context,
+ @NonNull Context context,
@NonNull String packageName,
- Notification notification,
- LocalBluetoothManager localBluetoothManager) {
- super(context, notification);
-
+ @NonNull LocalBluetoothManager localBluetoothManager) {
+ mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
}
@@ -115,7 +150,6 @@ public abstract class InfoMediaManager extends MediaManager {
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -127,17 +161,14 @@ public abstract class InfoMediaManager extends MediaManager {
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
- return new RouterInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
- return new NoOpInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
}
} else {
- return new ManagerInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
}
}
@@ -239,6 +270,38 @@ public abstract class InfoMediaManager extends MediaManager {
return null;
}
+ protected final void registerCallback(MediaDeviceCallback callback) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ protected final void unregisterCallback(MediaDeviceCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListAdded(new ArrayList<>(devices));
+ }
+ }
+
+ private void dispatchConnectedDeviceChanged(String id) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onConnectedDeviceChanged(id);
+ }
+ }
+
+ protected void dispatchOnRequestFailed(int reason) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onRequestFailed(reason);
+ }
+ }
+
+ private Collection<MediaDeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
/**
* Get current device that played media.
* @return MediaDevice
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 59254925dbcf..63056b6dc8c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,8 +138,7 @@ public class LocalMediaManager implements BluetoothCallback {
}
mInfoMediaManager =
- InfoMediaManager.createInstance(
- context, packageName, notification, mLocalBluetoothManager);
+ InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
}
/**
@@ -505,9 +504,9 @@ public class LocalMediaManager implements BluetoothCallback {
return new CopyOnWriteArrayList<>(mCallbacks);
}
- class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+ class MediaDeviceCallback implements InfoMediaManager.MediaDeviceCallback {
@Override
- public void onDeviceListAdded(List<MediaDevice> devices) {
+ public void onDeviceListAdded(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.clear();
mMediaDevices.addAll(devices);
@@ -637,7 +636,7 @@ public class LocalMediaManager implements BluetoothCallback {
}
@Override
- public void onDeviceListRemoved(List<MediaDevice> devices) {
+ public void onDeviceListRemoved(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.removeAll(devices);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 453e807947cf..c4fac358c01a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -54,9 +53,8 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
/* package */ ManagerInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
deleted file mode 100644
index d562c8a82f2d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.media;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.content.Context;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * MediaManager provide interface to get MediaDevice list.
- */
-public abstract class MediaManager {
-
- protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
-
- protected Context mContext;
- protected Notification mNotification;
-
- MediaManager(Context context, Notification notification) {
- mContext = context;
- mNotification = notification;
- }
-
- protected void registerCallback(MediaDeviceCallback callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- }
-
- protected void unregisterCallback(MediaDeviceCallback callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- }
- }
-
- protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListAdded(new ArrayList<>(devices));
- }
- }
-
- protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListRemoved(devices);
- }
- }
-
- protected void dispatchConnectedDeviceChanged(String id) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onConnectedDeviceChanged(id);
- }
- }
-
- protected void dispatchOnRequestFailed(int reason) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onRequestFailed(reason);
- }
- }
-
- private Collection<MediaDeviceCallback> getCallbacks() {
- return new CopyOnWriteArrayList<>(mCallbacks);
- }
-
- /**
- * Callback for notifying device is added, removed and attributes changed.
- */
- public interface MediaDeviceCallback {
-
- /**
- * Callback for notifying MediaDevice list is added.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListAdded(List<MediaDevice> devices);
-
- /**
- * Callback for notifying MediaDevice list is removed.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListRemoved(List<MediaDevice> devices);
-
- /**
- * Callback for notifying connected MediaDevice is changed.
- *
- * @param id the id of MediaDevice
- */
- void onConnectedDeviceChanged(String id);
-
- /**
- * Callback for notifying that transferring is failed.
- *
- * @param reason the reason that the request has failed. Can be one of followings:
- * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
- * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
- * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
- */
- void onRequestFailed(int reason);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ea4de392e139..ff4d4dd837d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
@@ -42,9 +41,8 @@ import java.util.List;
NoOpInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index df03167cd0f9..9c82cb1ef57d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -17,7 +17,6 @@
package com.android.settingslib.media;
import android.annotation.SuppressLint;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
@@ -71,10 +70,9 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
/* package */ RouterInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
MediaRouter2 router = null;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index c647cbb5a0b1..f0185b95e8f4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,22 +64,21 @@ public class InfoMediaManagerIntegTest {
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
- InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -87,7 +86,7 @@ public class InfoMediaManagerIntegTest {
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
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 c159d5ee37f0..d85d2534f856 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
@@ -91,7 +91,7 @@ public class InfoMediaManagerTest {
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
- private MediaManager.MediaDeviceCallback mCallback;
+ private InfoMediaManager.MediaDeviceCallback mCallback;
@Mock
private MediaSessionManager mMediaSessionManager;
@Mock
@@ -109,8 +109,7 @@ public class InfoMediaManagerTest {
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new ManagerInfoMediaManager(
- mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 9a7d4f1540df..693b7d0faa79 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -35,7 +36,6 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -75,8 +75,6 @@ public class LocalMediaManagerTest {
private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
- private InfoMediaManager mInfoMediaManager;
- @Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private LocalMediaManager.DeviceCallback mCallback;
@@ -87,8 +85,6 @@ public class LocalMediaManagerTest {
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
- private MediaRouter2Manager mMediaRouter2Manager;
- @Mock
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
@@ -97,6 +93,7 @@ public class LocalMediaManagerTest {
private Context mContext;
private LocalMediaManager mLocalMediaManager;
+ private InfoMediaManager mInfoMediaManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private InfoMediaDevice mInfoMediaDevice1;
private InfoMediaDevice mInfoMediaDevice2;
@@ -116,10 +113,16 @@ public class LocalMediaManagerTest {
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ // Need to call constructor to initialize final fields.
+ mInfoMediaManager = mock(
+ InfoMediaManager.class,
+ withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+
mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
- mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
- mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager =
+ new LocalMediaManager(
+ mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
mLocalMediaManager.mAudioManager = mAudioManager;
}
@@ -146,7 +149,6 @@ public class LocalMediaManagerTest {
mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
-
verify(mInfoMediaManager).connectToDevice(device);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
deleted file mode 100644
index c3237f0e72eb..000000000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.media;
-
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaManagerTest {
-
- private static final String TEST_ID = "test_id";
-
- @Mock
- private MediaManager.MediaDeviceCallback mCallback;
- @Mock
- private MediaDevice mDevice;
-
- private MediaManager mMediaManager;
- private Context mContext;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
-
- when(mDevice.getId()).thenReturn(TEST_ID);
-
- mMediaManager = new MediaManager(mContext, null) {};
- }
-
- @Test
- public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListAdded(Collections.emptyList());
-
- verify(mCallback).onDeviceListAdded(any());
- }
-
- @Test
- public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListRemoved(Collections.emptyList());
-
- verify(mCallback).onDeviceListRemoved(Collections.emptyList());
- }
-
- @Test
- public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
-
- verify(mCallback).onConnectedDeviceChanged(TEST_ID);
- }
-
- @Test
- public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchOnRequestFailed(1);
-
- verify(mCallback).onRequestFailed(1);
- }
-
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index add313419c7d..8f8445d7a40b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -115,6 +115,10 @@ public class GlobalSettings {
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+ Settings.Global.Wearable.AUTO_BEDTIME_MODE,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0a076095434..6def40be7977 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -452,6 +452,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.AUTO_BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 28cdc6db192b..09d076ee9c26 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -626,9 +626,6 @@ public class SettingsBackupTest {
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c02f56ab294..eb2d13dc9eb5 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -566,9 +566,6 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <!-- Permission required for CTS test - android.server.biometrics -->
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
-
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index baa139727d72..d05d40d969de 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@ flag {
}
flag {
+ name: "notification_view_flipper_pausing"
+ namespace: "systemui"
+ description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+ bug: "309146176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc74951d332..b1240252796f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@ import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
+import android.util.Log
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@ class TextAnimator(
}
sealed class PositionedGlyph {
-
/** Mutable X coordinate of the glyph position relative from drawing offset. */
var x: Float = 0f
@@ -269,41 +269,53 @@ class TextAnimator(
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
delay: Long = 0,
- onAnimationEnd: Runnable? = null
+ onAnimationEnd: Runnable? = null,
+ ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+ private fun setTextStyleInternal(
+ fvar: String?,
+ textSize: Float,
+ color: Int?,
+ strokeWidth: Float,
+ animate: Boolean,
+ duration: Long,
+ interpolator: TimeInterpolator?,
+ delay: Long,
+ onAnimationEnd: Runnable?,
+ updateLayoutOnFailure: Boolean,
) {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
- }
-
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
-
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
+ try {
+ if (animate) {
+ animator.cancel()
+ textInterpolator.rebase()
+ }
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
- }
- textInterpolator.onTargetPaintModified()
-
- if (animate) {
- animator.startDelay = delay
- animator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- val listener =
- object : AnimatorListenerAdapter() {
+ if (textSize >= 0) {
+ textInterpolator.targetPaint.textSize = textSize
+ }
+ if (!fvar.isNullOrBlank()) {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+ }
+ if (color != null) {
+ textInterpolator.targetPaint.color = color
+ }
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
+ textInterpolator.onTargetPaintModified()
+
+ if (animate) {
+ animator.startDelay = delay
+ animator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { animator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
@@ -312,14 +324,25 @@ class TextAnimator(
animator.removeListener(this)
}
}
- animator.addListener(listener)
+ animator.addListener(listener)
+ }
+ animator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
+ }
+ } catch (ex: IllegalArgumentException) {
+ if (updateLayoutOnFailure) {
+ Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+ " due to the layout having changed unexpectedly without being notified.", ex)
+ updateLayout(textInterpolator.layout)
+ setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+ } else {
+ throw ex
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
}
}
@@ -355,15 +378,13 @@ class TextAnimator(
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
- ) {
- val fvar = fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
- setTextStyle(
- fvar = fvar,
+ ) = setTextStyleInternal(
+ fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ ),
textSize = textSize,
color = color,
strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@ class TextAnimator(
interpolator = interpolator,
delay = delay,
onAnimationEnd = onAnimationEnd,
+ updateLayoutOnFailure = true,
)
+
+ companion object {
+ private val TAG = TextAnimator::class.simpleName!!
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 515c8169f1c4..078da1c863ce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -31,8 +31,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -93,7 +93,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -119,7 +118,6 @@ import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@@ -199,37 +197,26 @@ fun CommunalHub(
}
},
) {
- Column(Modifier.align(Alignment.TopStart)) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
- // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
- if (viewModel is CommunalViewModel) {
- val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
- Spacer(Modifier.height(24.dp))
- LockStateIcon(
- isUnlocked = isUnlocked,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
- }
- }
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
@@ -281,7 +268,7 @@ fun CommunalHub(
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun ColumnScope.CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
@@ -295,7 +282,7 @@ private fun ColumnScope.CommunalHubLazyGrid(
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
- Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
+ Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -377,26 +364,6 @@ private fun ColumnScope.CommunalHubLazyGrid(
}
}
-@Composable
-private fun LockStateIcon(
- isUnlocked: Boolean,
- modifier: Modifier = Modifier,
-) {
- val colors = LocalAndroidColorScheme.current
- val resource =
- if (isUnlocked) {
- R.drawable.ic_unlocked
- } else {
- R.drawable.ic_lock
- }
- Icon(
- painter = painterResource(id = resource),
- contentDescription = null,
- tint = colors.onPrimaryContainer,
- modifier = modifier.size(52.dp)
- )
-}
-
/**
* Toolbar that contains action buttons to
* 1) open the widget picker
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 92eb8f8c36c2..4950b96b077f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -45,32 +45,32 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -102,6 +102,7 @@ private const val SENSOR_HEIGHT = 60
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -148,28 +149,11 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Before
fun setup() {
- testScope = TestScope(StandardTestDispatcher())
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractor(
- powerRepository,
- mock(FalsingCollector::class.java),
- mock(ScreenOffAnimationController::class.java),
- statusBarStateController,
- )
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = {
- mock(FromLockscreenTransitionInteractor::class.java)
- },
- fromPrimaryBouncerTransitionInteractor = {
- mock(FromPrimaryBouncerTransitionInteractor::class.java)
- },
- fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) },
- )
+ testScope = kosmos.testScope
+ powerRepository = kosmos.fakePowerRepository
+ powerInteractor = kosmos.powerInteractor
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b80781f..563aad1920f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,7 +39,6 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -114,7 +113,6 @@ class CommunalViewModelTest : SysuiTestCase() {
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
- kosmos.deviceEntryInteractor,
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index fb46ed9d54ed..b3380ff6409c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -25,18 +25,17 @@ import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,9 +48,10 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
private lateinit var underTest: LightRevealScrimRepositoryImpl
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -59,13 +59,13 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- fakeKeyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractorFactory.create(repository = powerRepository).powerInteractor
-
underTest =
- LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock())
+ LightRevealScrimRepositoryImpl(
+ kosmos.fakeKeyguardRepository,
+ context,
+ kosmos.powerInteractor,
+ mock()
+ )
}
@Test
@@ -168,8 +168,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo1AfterAnimationStarted() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(500L)
@@ -179,8 +180,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_startingRevealTwiceWontRerunAnimator() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(250L)
@@ -193,12 +195,14 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
- runTest(UnconfinedTestDispatcher()) {
- val value by collectLastValue(underTest.revealAmount)
+ testScope.runTest {
+ val lastValue by collectLastValue(underTest.revealAmount)
+ runCurrent()
+ underTest.startRevealAmountAnimator(true)
+ animatorTestRule.advanceTimeBy(500L)
underTest.startRevealAmountAnimator(false)
- assertEquals(1.0f, value)
animatorTestRule.advanceTimeBy(500L)
- assertEquals(0.0f, value)
+ assertEquals(0.0f, lastValue)
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index f9ec3d161bb0..24c651f3c702 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
@@ -120,6 +122,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testKeyguardGuardVisibilityStopsSecureCamera() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -144,6 +147,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testBouncerShowingResetsSecureCameraState() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -166,6 +170,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
val isVisible = collectLastValue(underTest.isKeyguardVisible)
repository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 000000000000..c80eb135f1a5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+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.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val batteryController = FakeBatteryController(LeakCheck())
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+ Truth.assertThat(availability).hasSize(1)
+ Truth.assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerPowerSaving() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+ batteryController.setPowerSaveMode(true)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+ batteryController.setPowerSaveMode(false)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerIsPluggedIn() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+ batteryController.isPluggedIn = true
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+ batteryController.isPluggedIn = false
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..62c51e6252ce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = FakeBatteryController(LeakCheck())
+ private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+ @Test
+ fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+ )
+
+ Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+ controller.setPowerSaveMode(false)
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(
+ BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+ )
+ )
+
+ Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleLongClick() = runTest {
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 000000000000..6e9db2cbef07
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+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.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+ private lateinit var mapper: BatterySaverTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ BatterySaverTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ )
+ }
+
+ @Test
+ fun map_standard_notPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_notPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.standard_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.standard_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.extreme_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.extreme_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createBatterySaverTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ ): QSTileState {
+ val label = context.getString(R.string.battery_detail_switch_title)
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else 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/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
new file mode 100644
index 000000000000..39755bf8d764
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.internet.domain
+
+import android.graphics.drawable.TestStubDrawable
+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.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val internetTileConfig = kosmos.qsInternetTileConfig
+ private val mapper by lazy {
+ InternetTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+ addOverride(wifiRes, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ context
+ )
+ }
+
+ @Test
+ fun withActiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ iconId = wifiRes,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_internet_label),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_networks_available),
+ Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+ context.getString(R.string.quick_settings_internet_label)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withInactiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_networks_unavailable),
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
+ contentDescription = null
+ ),
+ context.getString(R.string.quick_settings_networks_unavailable)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createInternetTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ icon: Icon,
+ contentDescription: String,
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_internet_label)
+ return QSTileState(
+ { icon },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+
+ private companion object {
+ val wifiRes = WIFI_FULL_ICONS[4]
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
new file mode 100644
index 000000000000..37ef6ad17a64
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -0,0 +1,548 @@
+/*
+ * 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.internet.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileDataInteractorTest : SysuiTestCase() {
+ private val testUser = UserHandle.of(1)
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InternetTileDataInteractor
+ private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private val wifiRepository = FakeWifiRepository()
+ private val userSetupRepo = FakeUserSetupRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+ private val tableLogBuffer: TableLogBuffer = mock()
+ private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+ private val mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ private val mobileConnectionRepository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+ private val flags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+
+ private val internet = context.getString(R.string.quick_settings_internet_label)
+
+ @Before
+ fun setUp() {
+ mobileConnectionRepository.apply {
+ setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+
+ mobileConnectionsRepository.apply {
+ activeMobileDataRepository.value = mobileConnectionRepository
+ activeMobileDataSubscriptionId.value = SUB_1_ID
+ setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+ }
+
+ mobileIconsInteractor =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ userSetupRepo,
+ testScope.backgroundScope,
+ context,
+ flags,
+ )
+
+ context.orCreateTestableResources.apply {
+ addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable())
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4,
+ TestStubDrawable()
+ )
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable())
+ }
+
+ underTest =
+ InternetTileDataInteractor(
+ context,
+ testScope.backgroundScope,
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ )
+ }
+
+ @Test
+ fun noDefault_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+ assertThat(latest?.secondaryLabel)
+ .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ }
+
+ @Test
+ fun noDefault_networksAvailable() =
+ testScope.runTest {
+ // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable]
+ }
+
+ @Test
+ fun wifiDefaultAndActive() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ )
+ val wifiIcon =
+ WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
+ as WifiIcon.Visible
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+ assertThat(latest?.secondaryLabel).isNull()
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+
+ val actualIcon = latest?.icon
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo("$internet,test ssid")
+ val expectedSd = wifiIcon.contentDescription
+ assertThat(latest?.stateDescription).isEqualTo(expectedSd)
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotNone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .doesNotContain(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotTablet() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotLaptop() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotWatch() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotAuto() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotPhone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotUnknown() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotInvalid() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = emptyList()
+
+ assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_networksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ assertThat(latest?.stateDescription).isNull()
+ val expectedCd =
+ "$internet,${context.getString(R.string.quick_settings_networks_available)}"
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(expectedCd)
+ }
+
+ @Test
+ fun mobileDefault_usesNetworkNameAndIcon() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ assertThat(latest).isNotNull()
+ assertThat(latest?.secondaryTitle).isNotNull()
+ assertThat(latest?.secondaryTitle.toString()).contains("test network")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryTitle.toString())
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(internet)
+ }
+
+ @Test
+ fun ethernetDefault_validated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ @Test
+ fun ethernetDefault_notValidated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = hotspot,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ }
+
+ private companion object {
+ const val SUB_1_ID = 1
+
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..e1f3d97eb35c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.internet.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+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.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class InternetTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+ private lateinit var underTest: InternetTileUserActionInteractor
+
+ @Mock private lateinit var internetDialogManager: InternetDialogManager
+ @Mock private lateinit var controller: AccessPointController
+
+ @Before
+ fun setup() {
+ internetDialogManager = mock<InternetDialogManager>()
+ controller = mock<AccessPointController>()
+
+ underTest =
+ InternetTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ internetDialogManager,
+ controller,
+ inputHandler,
+ )
+ }
+
+ @Test
+ fun handleClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleLongClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c04c174..f6042e467987 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
android:layout_marginStart="49dp"
android:layout_marginEnd="49dp"
android:overScrollMode="never"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e181d079fc6d..35f6a08795bc 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -345,9 +345,6 @@
the notification is not swiped enough to dismiss it. -->
<bool name="config_showNotificationGear">true</bool>
- <!-- Whether or not a background should be drawn behind a notification. -->
- <bool name="config_drawNotificationBackground">false</bool>
-
<!-- Whether or the notifications can be shown and dismissed with a drag. -->
<bool name="config_enableNotificationShadeDrag">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b71341791ee6..5dbdd18bc81b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -75,6 +75,11 @@
<!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
<string name="battery_saver_dismiss_action">No thanks</string>
+ <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="standard_battery_saver_text">Standard</string>
+ <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="extreme_battery_saver_text">Extreme</string>
+
<!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
<!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f28d4052b5a8..8a2245d3d14c 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -404,7 +404,9 @@ constructor(
if (nextAlarmMillis > 0) nextAlarmMillis else null,
SysuiR.string::status_bar_alarm.name
)
- .also { data -> clock?.run { events.onAlarmDataChanged(data) } }
+ .also { data ->
+ mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 31698a35c811..01c2cc4fe874 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -345,11 +345,25 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
}
}
- private TextView loadPercentView() {
+ private TextView inflatePercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
}
+ private void addPercentView(TextView inflatedPercentView) {
+ mBatteryPercentView = inflatedPercentView;
+
+ if (mPercentageStyleId != 0) { // Only set if specified as attribute
+ mBatteryPercentView.setTextAppearance(mPercentageStyleId);
+ }
+ float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+ mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+ if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addView(mBatteryPercentView, new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ (int) Math.ceil(fontHeight)));
+ }
+
/**
* Updates percent view by removing old one and reinflating if necessary
*/
@@ -388,7 +402,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
(String estimate) -> {
if (mBatteryPercentView == null) {
- mBatteryPercentView = loadPercentView();
+ // Similar to the legacy behavior, inflate and add the view. We will
+ // only use it for the estimate text
+ addPercentView(inflatePercentView());
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
mEstimateText = estimate;
@@ -401,6 +417,10 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
}
});
} else {
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ mBatteryPercentView.setText(null);
+ }
updateContentDescription();
}
}
@@ -485,21 +505,18 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
return;
}
- if (mUnifiedBattery == null) {
- return;
- }
+ if (!mShowPercentAvailable || mUnifiedBattery == null) return;
- // TODO(b/140051051)
- final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
- .getIntForUser(getContext().getContentResolver(),
- SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
- ? 1 : 0, UserHandle.USER_CURRENT));
-
- boolean shouldShow =
- (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON;
- shouldShow = shouldShow && !mBatteryStateUnknown;
+ boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
+ if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
+ // Slow case: fall back to the system setting
+ // TODO(b/140051051)
+ shouldShow = 0 != whitelistIpcs(() -> Settings.System
+ .getIntForUser(getContext().getContentResolver(),
+ SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+ ? 1 : 0, UserHandle.USER_CURRENT));
+ }
setBatteryDrawableState(
new BatteryDrawableState(
@@ -534,17 +551,8 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
if (shouldShow) {
if (!showing) {
- mBatteryPercentView = loadPercentView();
- if (mPercentageStyleId != 0) { // Only set if specified as attribute
- mBatteryPercentView.setTextAppearance(mPercentageStyleId);
- }
- float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
- mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
- if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addPercentView(inflatePercentView());
updatePercentText();
- addView(mBatteryPercentView, new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- (int) Math.ceil(fontHeight)));
}
} else {
if (showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62db036..10a0d9584d05 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
package com.android.systemui.battery
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -15,4 +27,39 @@ interface BatterySaverModule {
@IntoMap
@StringKey(BatterySaverTile.TILE_SPEC)
fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+ companion object {
+ private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_battery_saver_icon_off,
+ labelRes = R.string.battery_detail_switch_title,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileViewModel(
+ factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+ mapper: BatterySaverTileMapper,
+ stateInteractor: BatterySaverTileDataInteractor,
+ userActionInteractor: BatterySaverTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
index 1b8495ace243..f3652b89cc50 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableWrapper
import android.view.Gravity
+import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.roundToInt
@@ -36,7 +37,7 @@ import kotlin.math.roundToInt
*/
@Suppress("RtlHardcoded")
class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
- /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */
+ /** One of [CENTER, LEFT]. Note that number text does not RTL. */
var gravity = Gravity.CENTER
set(value) {
field = value
@@ -67,8 +68,8 @@ class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
dr.setBounds(
bounds.left,
bounds.top,
- (bounds.left + dw).roundToInt(),
- (bounds.top + dh).roundToInt()
+ ceil(bounds.left + dw).toInt(),
+ ceil(bounds.top + dh).toInt()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
index 6d3206767d2b..5e34d2909d81 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -26,6 +26,7 @@ import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
import kotlin.math.floor
import kotlin.math.roundToInt
@@ -103,6 +104,11 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() {
// saveLayer is needed here so we don't clip the other layers of our drawable
canvas.saveLayer(null, null)
+ // Fill from the opposite direction in rtl mode
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ canvas.scale(-1f, 1f, bounds.width() / 2f, bounds.height() / 2f)
+ }
+
// We need to use 3 draw commands:
// 1. Clip to the current level
// 2. Clip anything outside of the path
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
index 199dd1f18a42..706b9ec563c9 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -26,7 +26,10 @@ import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.util.PathParser
import android.view.Gravity
+import android.view.View
import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlin.math.floor
import kotlin.math.roundToInt
/**
@@ -69,8 +72,11 @@ class BatteryLayersDrawable(
) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
- private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas)
- private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas)
+
+ private val attrFullCanvas = RectF()
+ private val attrRightCanvas = RectF()
+ private val scaledAttrFullCanvas = RectF()
+ private val scaledAttrRightCanvas = RectF()
var batteryState = batteryState
set(value) {
@@ -88,6 +94,12 @@ class BatteryLayersDrawable(
updateColors(batteryState.showErrorState, value)
}
+ init {
+ isAutoMirrored = true
+ // Initialize the canvas rects since they are not static
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ }
+
private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
if (new.showErrorState != old.showErrorState) {
updateColors(new.showErrorState, colors)
@@ -144,9 +156,42 @@ class BatteryLayersDrawable(
bounds.height() / Metrics.ViewportHeight
)
- // Scale the attribution bounds
- scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas)
- scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas)
+ scaleAttributionBounds()
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ scaleAttributionBounds()
+
+ return super.onLayoutDirectionChanged(layoutDirection)
+ }
+
+ private fun setAttrRects(rtl: Boolean) {
+ // Local refs make the math easier to parse
+ val full = Metrics.AttrFullCanvasInsets
+ val side = Metrics.AttrRightCanvasInsets
+ val sideRtl = Metrics.AttrRightCanvasInsetsRtl
+ val vh = Metrics.ViewportHeight
+ val vw = Metrics.ViewportWidth
+
+ attrFullCanvas.set(
+ if (rtl) full.right else full.left,
+ full.top,
+ vw - if (rtl) full.left else full.right,
+ vh - full.bottom,
+ )
+ attrRightCanvas.set(
+ if (rtl) sideRtl.left else side.left,
+ side.top,
+ vw - (if (rtl) sideRtl.right else side.right),
+ vh - side.bottom,
+ )
+ }
+
+ /** If bounds (i.e., scale), or RTL properties change, we have to recalculate the attr bounds */
+ private fun scaleAttributionBounds() {
+ scaleMatrix.mapRect(scaledAttrFullCanvas, attrFullCanvas)
+ scaleMatrix.mapRect(scaledAttrRightCanvas, attrRightCanvas)
}
override fun draw(canvas: Canvas) {
@@ -163,13 +208,14 @@ class BatteryLayersDrawable(
if (batteryState.showPercent && batteryState.attribution != null) {
// 4a. percent & attribution. Implies space-sharing
- // Configure the attribute to draw in a smaller bounding box and align left
+ // Configure the attribute to draw in a smaller bounding box and align left and use
+ // floor/ceil math to make sure we get every available pixel
attribution.gravity = Gravity.LEFT
attribution.setBounds(
- scaledAttrRightCanvas.left.roundToInt(),
- scaledAttrRightCanvas.top.roundToInt(),
- scaledAttrRightCanvas.right.roundToInt(),
- scaledAttrRightCanvas.bottom.roundToInt(),
+ floor(scaledAttrRightCanvas.left).toInt(),
+ floor(scaledAttrRightCanvas.top).toInt(),
+ ceil(scaledAttrRightCanvas.right).toInt(),
+ ceil(scaledAttrRightCanvas.bottom).toInt(),
)
attribution.draw(canvas)
@@ -196,16 +242,44 @@ class BatteryLayersDrawable(
*/
override fun setAlpha(alpha: Int) {}
+ /**
+ * Interface that describes relevant top-level metrics for the proper rendering of this icon.
+ * The overall canvas is defined as ViewportWidth x ViewportHeight, which is hard coded to 24x14
+ * points.
+ *
+ * The attr canvas insets are rect inset definitions. That is, they are defined as l,t,r,b
+ * points from the nearest edge. Note that for RTL, we don't actually flip the text since
+ * numbers do not reverse for RTL locales.
+ */
interface M {
val ViewportWidth: Float
val ViewportHeight: Float
- // Bounds, oriented in the above viewport, where we will fit-center and center-align
- // an attribution that is the sole foreground element
- val AttrFullCanvas: RectF
- // Bounds, oriented in the above viewport, where we will fit-center and left-align
- // an attribution that is sharing space with the percent text of the drawable
- val AttrRightCanvas: RectF
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the full canvas for a single
+ * foreground element. The element will be fit-center and center-aligned on this canvas
+ *
+ * 18x8 point size
+ */
+ val AttrFullCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in RTL, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsetsRtl: RectF
}
companion object {
@@ -220,20 +294,9 @@ class BatteryLayersDrawable(
override val ViewportWidth: Float = 24f
override val ViewportHeight: Float = 14f
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and center-align
- * an attribution that is the sole foreground element
- *
- * 18x8 point size
- */
- override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f)
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and left-align
- * an attribution that is sharing space with the percent text of the drawable
- *
- * 6x6 point size
- */
- override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f)
+ override val AttrFullCanvasInsets = RectF(4f, 3f, 2f, 3f)
+ override val AttrRightCanvasInsets = RectF(16f, 4f, 2f, 4f)
+ override val AttrRightCanvasInsetsRtl = RectF(14f, 4f, 4f, 4f)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
index 123d6ba57900..aa0e37348f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -23,6 +23,7 @@ import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -71,6 +72,7 @@ class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() {
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -81,11 +83,12 @@ class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() {
val totalAvailableWidth = CanvasWidth * hScale
val textWidth = textPaint.measureText(percentText)
val offsetX = (totalAvailableWidth - textWidth) / 2
+ val startOffset = if (rtl) ViewportInsetRight else ViewportInsetLeft
// Draw the text centered in the available area
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ (startOffset * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
index 0c418b9caa7d..3b4c77936cff 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
@@ -23,6 +23,7 @@ import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -94,6 +95,7 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() {
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -107,7 +109,7 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() {
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
@@ -128,6 +130,7 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() {
companion object {
private const val ViewportInsetLeft = 4f
+ private const val ViewportInsetLeftRtl = 2f
private const val ViewportInsetTop = 2f
private const val CanvasWidth = 12f
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 6af0fa069dbc..66aeda63e222 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -42,7 +42,7 @@ import com.android.settingslib.media.MediaOutputConstants;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -69,7 +69,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
private final Context mContext;
private final UiEventLogger mUiEventLogger;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final LocalBluetoothManager mLocalBluetoothManager;
private final BroadcastSender mBroadcastSender;
private final SystemUIDialog.Factory mSystemUIDialogFactory;
@@ -157,7 +157,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
@AssistedInject
BroadcastDialogDelegate(
Context context,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
@Nullable LocalBluetoothManager localBluetoothManager,
UiEventLogger uiEventLogger,
@Background Executor bgExecutor,
@@ -166,7 +166,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
@Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
@Assisted(OUTPUT_PKG_NAME) String outputPkgName) {
mContext = context;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mLocalBluetoothManager = localBluetoothManager;
mSystemUIDialogFactory = systemUIDialogFactory;
mCurrentBroadcastApp = currentBroadcastApp;
@@ -218,7 +218,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
changeOutput.setOnClickListener((view) -> {
- mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+ mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
dialog.dismiss();
});
cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 35b27aaeb6bc..fc9a7df4744d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,7 +21,6 @@ import com.android.systemui.communal.domain.interactor.CommunalTutorialInteracto
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -47,7 +46,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalViewModel
@Inject
@@ -56,7 +54,6 @@ constructor(
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
shadeInteractor: ShadeInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -90,8 +87,6 @@ constructor(
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
- val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
-
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 79455ebb624c..289dbd9f66e0 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -24,9 +24,12 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.model.BiometricMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -60,17 +63,23 @@ constructor(
private val context: Context,
activityStarter: ActivityStarter,
powerInteractor: PowerInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
- combine(
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isKeyguardShowing,
- primaryBouncerInteractor.isShowing,
- alternateBouncerInteractor.isVisible,
- ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
- occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
- }
- .distinctUntilChanged()
+ if (KeyguardWmStateRefactor.isEnabled) {
+ keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ } else {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isKeyguardShowing,
+ primaryBouncerInteractor.isShowing,
+ alternateBouncerInteractor.isVisible,
+ ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+ occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+ }
+ .distinctUntilChanged()
+ }
+
private val fingerprintUnlockSuccessEvents: Flow<Unit> =
fingerprintAuthRepository.authenticationStatus
.ifKeyguardOccludedByApp()
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a86938592..ed82278a7346 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@ constructor(
}
companion object {
- const val KEY_UP_TIMEOUT = 100L
+ const val KEY_UP_TIMEOUT = 60L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2e233d8e5dd2..3134e35a92e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -289,6 +289,8 @@ public class KeyguardService extends Service {
};
}
+ private final WindowManagerOcclusionManager mWmOcclusionManager;
+
@Inject
public KeyguardService(
KeyguardViewMediator keyguardViewMediator,
@@ -302,7 +304,8 @@ public class KeyguardService extends Service {
KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
@Application CoroutineScope scope,
FeatureFlags featureFlags,
- PowerInteractor powerInteractor) {
+ PowerInteractor powerInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -323,6 +326,8 @@ public class KeyguardService extends Service {
keyguardSurfaceBehindAnimator,
scope);
}
+
+ mWmOcclusionManager = windowManagerOcclusionManager;
}
@Override
@@ -414,7 +419,11 @@ public class KeyguardService extends Service {
Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
- mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ } else {
+ mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded);
+ }
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6d917bbde82b..a37397db81f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1365,7 +1365,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private WindowManagerOcclusionManager mWmOcclusionManager;
/**
+
* Injected constructor. See {@link KeyguardModule}.
*/
public KeyguardViewMediator(
@@ -1411,7 +1413,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1486,6 +1489,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
+
+ mWmOcclusionManager = wmOcclusionManager;
}
public void userActivity() {
@@ -2103,15 +2108,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public IRemoteAnimationRunner getOccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ }
}
+ /**
+ * TODO(b/326464548): Move this to WindowManagerOcclusionManager
+ */
public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
}
public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(
+ mWmOcclusionManager.getUnoccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ }
}
public boolean isHiding() {
@@ -2145,8 +2162,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
- && mDeviceInteractive);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
+ && mDeviceInteractive);
+ }
adjustStatusBarLocked();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 8ebcece940c2..00f50023b263 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -140,8 +140,14 @@ constructor(
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback
) {
- goingAwayRemoteAnimationFinishedCallback = finishedCallback
- keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ if (apps.isNotEmpty()) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ } else {
+ // Nothing to do here if we have no apps, end the animation, which will cancel it and WM
+ // will make *something* visible.
+ finishedCallback.onAnimationFinished()
+ }
}
fun onKeyguardGoingAwayRemoteAnimationCancelled() {
@@ -174,13 +180,19 @@ constructor(
* if so, true should be the right choice.
*/
private fun setWmLockscreenState(
- lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also {
- Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " +
- "because setAodVisible was called before the first setLockscreenShown " +
- "call during boot. This is not typical, but is theoretically possible. " +
- "If you're investigating the lockscreen showing unexpectedly, start here.")
- },
- aodVisible: Boolean = this.isAodVisible
+ lockscreenShowing: Boolean =
+ this.isLockscreenShowing
+ ?: true.also {
+ Log.d(
+ TAG,
+ "Using isLockscreenShowing=true default in setWmLockscreenState, " +
+ "because setAodVisible was called before the first " +
+ "setLockscreenShown call during boot. This is not typical, but is " +
+ "theoretically possible. If you're investigating the lockscreen " +
+ "showing unexpectedly, start here."
+ )
+ },
+ aodVisible: Boolean = this.isAodVisible
) {
Log.d(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
new file mode 100644
index 000000000000..aab90c378a19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Matrix
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationTarget
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val UNOCCLUDE_ANIMATION_DURATION = 250
+private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
+
+/**
+ * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in
+ * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the
+ * lockscreen - we're still locked, but the user can interact with the activity.
+ *
+ * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick
+ * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this,
+ * and Maps Navigation.
+ *
+ * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED]
+ * activity is on top of the task stack, even if the device is unlocked and the keyguard is not
+ * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the
+ * keyguard and an activity is displaying over it.
+ *
+ * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're
+ * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to
+ * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing,
+ * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with
+ * [KeyguardTransitionInteractor] state.
+ *
+ * This is a very sensitive piece of state that has caused many headaches in the past. Please be
+ * careful.
+ */
+@SysUISingleton
+class WindowManagerOcclusionManager
+@Inject
+constructor(
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val activityTransitionAnimator: ActivityTransitionAnimator,
+ val keyguardViewController: dagger.Lazy<KeyguardViewController>,
+ val powerInteractor: PowerInteractor,
+ val context: Context,
+ val interactionJankMonitor: InteractionJankMonitor,
+ @Main executor: Executor,
+ val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ val occlusionInteractor: KeyguardOcclusionInteractor,
+) {
+ val powerButtonY =
+ context.resources.getDimensionPixelSize(
+ R.dimen.physical_power_button_center_screen_location_y
+ )
+ val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+
+ var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * launched and Window Manager wants us to animate it in. This is used as a signal that we are
+ * now occluded, and should update our state accordingly.
+ */
+ val occludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ occludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ occludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = true,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ activityTransitionAnimator
+ .createRunner(occludeAnimationController)
+ .onAnimationStart(
+ transit,
+ apps,
+ wallpapers,
+ nonApps,
+ occludeAnimationFinishedCallback,
+ )
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled")
+ }
+ }
+
+ var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * finished and Window Manager wants us to animate it out. This is used as a signal that we are
+ * no longer occluded, and should update our state accordingly.
+ *
+ * TODO(b/326464548): Restore dream specific animation.
+ */
+ val unoccludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ var unoccludeAnimator: ValueAnimator? = null
+ val unoccludeMatrix = Matrix()
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ unoccludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ unoccludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = false,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ interactionJankMonitor.begin(
+ createInteractionJankMonitorConf(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION,
+ "UNOCCLUDE"
+ )
+ )
+ if (apps.isEmpty()) {
+ Log.d(
+ TAG,
+ "No apps provided to unocclude runner; " +
+ "skipping animation and unoccluding."
+ )
+ unoccludeAnimationFinishedCallback?.onAnimationFinished()
+ return
+ }
+ val target = apps[0]
+ val localView: View = keyguardViewController.get().getViewRootImpl().getView()
+ val applier = SyncRtSurfaceTransactionApplier(localView)
+ // TODO(
+ executor.execute {
+ unoccludeAnimator?.cancel()
+ unoccludeAnimator =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = UNOCCLUDE_ANIMATION_DURATION.toLong()
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener { animation: ValueAnimator ->
+ val animatedValue = animation.animatedValue as Float
+ val surfaceHeight: Float =
+ target.screenSpaceBounds.height().toFloat()
+
+ unoccludeMatrix.setTranslate(
+ 0f,
+ (1f - animatedValue) *
+ surfaceHeight *
+ UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT
+ )
+
+ SurfaceParams.Builder(target.leash)
+ .withAlpha(animatedValue)
+ .withMatrix(unoccludeMatrix)
+ .withCornerRadius(windowCornerRadius)
+ .build()
+ .also { applier.scheduleApply(it) }
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ try {
+ unoccludeAnimationFinishedCallback
+ ?.onAnimationFinished()
+ unoccludeAnimator = null
+ interactionJankMonitor.end(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION
+ )
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ )
+ start()
+ }
+ }
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled")
+ context.mainExecutor.execute { unoccludeAnimator?.cancel() }
+ Log.d(TAG, "Unocclude animation cancelled.")
+ interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION)
+ }
+ }
+
+ /**
+ * Called when Window Manager tells the KeyguardService directly that we're occluded or not
+ * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion
+ * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while
+ * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants
+ * to make sure that we're in the correct state.
+ */
+ fun onKeyguardServiceSetOccluded(occluded: Boolean) {
+ Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)")
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded)
+ }
+
+ @VisibleForTesting
+ val occludeAnimationController: ActivityTransitionAnimator.Controller =
+ object : ActivityTransitionAnimator.Controller {
+
+ override var transitionContainer: ViewGroup
+ get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
+ set(_) {
+ // Should never be set.
+ }
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun createAnimatorState(): TransitionAnimator.State {
+ val fullWidth = transitionContainer.width
+ val fullHeight = transitionContainer.height
+
+ if (
+ keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value
+ ) {
+ val initialHeight = fullHeight / 3f
+ val initialWidth = fullWidth / 3f
+
+ // Start the animation near the power button, at one-third size, since the
+ // camera was launched from the power button.
+ return TransitionAnimator.State(
+ top = (powerButtonY - initialHeight / 2f).toInt(),
+ bottom = (powerButtonY + initialHeight / 2f).toInt(),
+ left = (fullWidth - initialWidth).toInt(),
+ right = fullWidth,
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ } else {
+ val initialHeight = fullHeight / 2f
+ val initialWidth = fullWidth / 2f
+
+ // Start the animation in the center of the screen, scaled down to half
+ // size.
+ return TransitionAnimator.State(
+ top = (fullHeight - initialHeight).toInt() / 2,
+ bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(),
+ left = (fullWidth - initialWidth).toInt() / 2,
+ right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(),
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ }
+ }
+ }
+
+ private fun createInteractionJankMonitorConf(
+ cuj: Int,
+ tag: String?
+ ): InteractionJankMonitor.Configuration.Builder {
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ cuj,
+ keyguardViewController.get().getViewRootImpl().view
+ )
+ return if (tag != null) builder.setTag(tag) else builder
+ }
+
+ companion object {
+ val TAG = "WindowManagerOcclusion"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 968c3e3a6792..5306645bf69f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -50,6 +50,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
+import com.android.systemui.keyguard.WindowManagerOcclusionManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -163,7 +164,8 @@ public interface KeyguardModule {
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -209,7 +211,8 @@ public interface KeyguardModule {
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
- keyguardInteractor);
+ keyguardInteractor,
+ windowManagerOcclusionManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
new file mode 100644
index 000000000000..e3654b415017
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Information about the SHOW_WHEN_LOCKED activity that is either newly on top of the task stack, or
+ * newly not on top of the task stack.
+ */
+data class ShowWhenLockedActivityInfo(
+ /** Whether the activity is on top. If not, we're unoccluding and will be animating it out. */
+ val isOnTop: Boolean,
+
+ /**
+ * Information about the activity, which we use for transition internals and also to customize
+ * animations.
+ */
+ val taskInfo: RunningTaskInfo? = null
+) {
+ fun isDream(): Boolean {
+ return taskInfo?.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+}
+
+/**
+ * Maintains state about "occluding" activities - activities with FLAG_SHOW_WHEN_LOCKED, which are
+ * capable of displaying over the lockscreen while the device is still locked (such as Google Maps
+ * navigation).
+ *
+ * Window Manager considers the device to be in the "occluded" state whenever such an activity is on
+ * top of the task stack, including while we're unlocked, while keyguard code considers us to be
+ * occluded only when we're locked, with an occluding activity currently displaying over the
+ * lockscreen.
+ *
+ * This dual definition is confusing, so this repository collects all of the signals WM gives us,
+ * and consolidates them into [showWhenLockedActivityInfo.isOnTop], which is the actual question WM
+ * is answering when they say whether we're 'occluded'. Keyguard then uses this signal to
+ * conditionally transition to [KeyguardState.OCCLUDED] where appropriate.
+ */
+@SysUISingleton
+class KeyguardOcclusionRepository @Inject constructor() {
+ val showWhenLockedActivityInfo = MutableStateFlow(ShowWhenLockedActivityInfo(isOnTop = false))
+
+ /**
+ * Sets whether there's a SHOW_WHEN_LOCKED activity on top of the task stack, and optionally,
+ * information about the activity itself.
+ *
+ * If no value is provided for [taskInfo], we'll default to the current [taskInfo].
+ *
+ * The [taskInfo] is always present when this method is called from the occlude/unocclude
+ * animation runners. We use the default when calling from [KeyguardService.isOccluded], since
+ * we only receive a true/false value there. isOccluded is mostly redundant - it's almost always
+ * called with true after an occlusion animation has started, and with false after an unocclude
+ * animation has started. In those cases, we don't want to clear out the taskInfo just because
+ * it wasn't available at that call site.
+ */
+ fun setShowWhenLockedActivityInfo(
+ onTop: Boolean,
+ taskInfo: RunningTaskInfo? = showWhenLockedActivityInfo.value.taskInfo
+ ) {
+ showWhenLockedActivityInfo.value =
+ ShowWhenLockedActivityInfo(
+ isOnTop = onTop,
+ taskInfo = taskInfo,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 64e28700aa74..3a6423d680c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -95,6 +95,7 @@ interface KeyguardRepository {
val isKeyguardShowing: Flow<Boolean>
/** Is an activity showing over the keyguard? */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 9b3f13d16911..d9479de7e25b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -75,7 +75,6 @@ constructor(
powerInteractor: PowerInteractor,
private val scrimLogger: ScrimLogger,
) : LightRevealScrimRepository {
-
companion object {
val TAG = LightRevealScrimRepository::class.simpleName!!
}
@@ -156,7 +155,11 @@ constructor(
override fun startRevealAmountAnimator(reveal: Boolean) {
if (reveal == willBeOrIsRevealed) return
willBeOrIsRevealed = reveal
- if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
+ if (reveal && !revealAmountAnimator.isRunning) {
+ revealAmountAnimator.start()
+ } else {
+ revealAmountAnimator.reverse()
+ }
scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 6aed944ac809..88ddfd4f4347 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -46,13 +47,16 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.ALTERNATE_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -112,6 +116,11 @@ constructor(
}
private fun listenForAlternateBouncerToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAlternateBouncer.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sampleUtil(finishedKeyguardState, ::Pair)
@@ -149,6 +158,10 @@ constructor(
}
}
+ fun dismissAlternateBouncer() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
companion object {
const val TAG = "FromAlternateBouncerTransitionInteractor"
val TRANSITION_DURATION_MS = 300.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index dbd5e26eacfc..9040e031d54e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -27,14 +27,17 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -47,29 +50,118 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
- listenForAodToLockscreen()
+ listenForAodToAwake()
+ listenForAodToOccluded()
listenForAodToPrimaryBouncer()
listenForAodToGone()
- listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
/**
+ * Listen for the signal that we're waking up and figure what state we need to transition to.
+ */
+ private fun listenForAodToAwake() {
+ val transitionToLockscreen: suspend (TransitionStep) -> UUID? =
+ { startedStep: TransitionStep ->
+ val modeOnCanceled =
+ if (startedStep.from == KeyguardState.LOCKSCREEN) {
+ TransitionModeOnCanceled.REVERSE
+ } else if (startedStep.from == KeyguardState.GONE) {
+ TransitionModeOnCanceled.RESET
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = KeyguardState.LOCKSCREEN,
+ modeOnCanceled = modeOnCanceled,
+ )
+ }
+
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // The refactor uses PowerInteractor's wakefulness, which is the earliest wake signal
+ // available. We have all of the information we need at this time to make a decision
+ // about where to transition.
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ // React only to wake events.
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // Make sure we've at least STARTED a transition to AOD.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.AOD }
+ .collect { (_, startedStep, biometricUnlockState, primaryBouncerShowing) ->
+ // Check with the superclass to see if an occlusion transition is needed.
+ // Also, don't react to wake and unlock events, as we'll be receiving a call
+ // to #dismissAod() shortly when the authentication completes.
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(startedStep)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.FINISH)
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ .collect {
+ (
+ _,
+ isKeyguardShowing,
+ lastStartedStep,
+ occluded,
+ biometricUnlockState,
+ primaryBouncerShowing) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ isKeyguardShowing &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(lastStartedStep)
+ }
+ }
+ }
+ }
+ }
+
+ /**
* There are cases where the transition to AOD begins but never completes, such as tapping power
* during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
* run AOD->OCCLUDED.
*/
private fun listenForAodToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(startedKeyguardTransitionStep, ::Pair)
@@ -84,49 +176,6 @@ constructor(
}
}
- private fun listenForAodToLockscreen() {
- scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.FINISH)
- .sample(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.biometricUnlockState,
- keyguardInteractor.primaryBouncerShowing,
- )
- .collect {
- (
- _,
- isKeyguardShowing,
- lastStartedStep,
- occluded,
- biometricUnlockState,
- primaryBouncerShowing) ->
- if (
- lastStartedStep.to == KeyguardState.AOD &&
- !occluded &&
- !isWakeAndUnlock(biometricUnlockState) &&
- isKeyguardShowing &&
- !primaryBouncerShowing
- ) {
- val modeOnCanceled =
- if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
- TransitionModeOnCanceled.REVERSE
- } else if (lastStartedStep.from == KeyguardState.GONE) {
- TransitionModeOnCanceled.RESET
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = KeyguardState.LOCKSCREEN,
- modeOnCanceled = modeOnCanceled,
- )
- }
- }
- }
- }
-
/**
* If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
* PRIMARY_BOUNCER.
@@ -145,6 +194,7 @@ constructor(
private fun listenForAodToGone() {
if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAod.
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8591fe782d3a..57b2a632008a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -34,6 +35,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,18 +48,22 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDozingToAny()
+ listenForWakeFromDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -70,6 +76,10 @@ constructor(
}
private fun listenForDozingToAny() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
powerInteractor.isAwake
.debounce(50L)
@@ -112,6 +122,58 @@ constructor(
}
}
+ /** Figure out what state to transition to when we awake from DOZING. */
+ private fun listenForWakeFromDozing() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ keyguardInteractor.biometricUnlockState,
+ canDismissLockScreen,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // If we haven't at least STARTED a transition to DOZING, ignore.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.DOZING }
+ .collect {
+ (
+ _,
+ _,
+ isIdleOnCommunal,
+ biometricUnlockState,
+ canDismissLockscreen,
+ primaryBouncerShowing) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ // Handled by dismissFromDozing().
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
+ startTransitionTo(
+ if (canDismissLockscreen) {
+ KeyguardState.GONE
+ } else if (primaryBouncerShowing) {
+ KeyguardState.PRIMARY_BOUNCER
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ )
+ }
+ }
+ }
+ }
+
+ /** Dismisses keyguard from the DOZING state. */
+ fun dismissFromDozing() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index a6cdaa8c6761..6433d0ede796 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -25,6 +25,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -46,12 +47,16 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 28282c630b8f..31371384e338 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,10 +23,13 @@ import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -35,6 +38,7 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -48,18 +52,23 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDreamingToOccluded()
listenForDreamingToGoneWhenDismissable()
listenForDreamingToGoneFromBiometricUnlock()
+ listenForDreamingToLockscreen()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -78,6 +87,7 @@ constructor(
fun startToLockscreenTransition() {
scope.launch {
+ KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
if (
transitionInteractor.startedKeyguardState.replayCache.last() ==
KeyguardState.DREAMING
@@ -88,16 +98,52 @@ constructor(
}
private fun listenForDreamingToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ combine(
+ keyguardInteractor.isDreaming,
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, ::toTriple)
+ .filter { (isDreaming, _, startedStep) ->
+ !isDreaming && startedStep.to == KeyguardState.DREAMING
+ }
+ .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+ }
+ } else {
+ scope.launch {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, Utils.Companion::toTriple)
+ .collect { (isOccluded, isDreaming, lastStartedTransition) ->
+ if (
+ isOccluded &&
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING
+ ) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToLockscreen() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
- combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
- .sample(startedKeyguardTransitionStep, ::toTriple)
- .collect { (isOccluded, isDreaming, lastStartedTransition) ->
- if (
- isOccluded &&
- !isDreaming &&
- lastStartedTransition.to == KeyguardState.DREAMING
- ) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(startedKeyguardState)
+ .collect { startedState ->
+ if (startedState == KeyguardState.DREAMING) {
+ startTransitionTo(KeyguardState.LOCKSCREEN)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 786c3c6697d9..51bc3ae778e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -23,6 +23,7 @@ import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -35,6 +36,7 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -49,14 +51,18 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GLANCEABLE_HUB,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
+
override fun start() {
if (!Flags.communalHub()) {
return
@@ -151,14 +157,27 @@ constructor(
}
private fun listenForHubToOccluded() {
- scope.launch {
- and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
- .sample(startedKeyguardState, ::Pair)
- .collect { (isOccludedAndNotDreaming, keyguardState) ->
- if (isOccludedAndNotDreaming && keyguardState == fromState) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> onTop }
+ .sample(startedKeyguardState)
+ .collect {
+ if (it == KeyguardState.GLANCEABLE_HUB) {
+ maybeStartTransitionToOccludedOrInsecureCamera()
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccludedAndNotDreaming, keyguardState) ->
+ if (isOccludedAndNotDreaming && keyguardState == fromState) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7593ac252543..d5a9bd19d766 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,8 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -34,6 +36,8 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,14 +50,18 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -65,23 +73,46 @@ constructor(
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
- scope.launch {
- keyguardInteractor.isKeyguardShowing
- .sample(
- startedKeyguardState,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
- if (isKeyguardShowing && startedState == KeyguardState.GONE) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ biometricSettingsRepository.isCurrentUserInLockdown
+ .distinctUntilChanged()
+ .filter { inLockdown -> inLockdown }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ if (startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to, ownerReason = "User initiated lockdown")
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardShowing
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
}
}
@@ -122,24 +153,10 @@ constructor(
private fun listenForGoneToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
- startTransitionTo(
- toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
- }
- }
+ listenForSleepTransition(
+ from = KeyguardState.GONE,
+ modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cb1571e7d702..bcad3324b258 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,7 +33,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
@@ -44,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -62,21 +62,24 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.LOCKSCREEN,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToGoneDragging()
- listenForLockscreenToOccluded()
+ listenForLockscreenToOccludedOrDreaming()
listenForLockscreenToAodOrDozing()
listenForLockscreenToPrimaryBouncer()
listenForLockscreenToDreaming()
@@ -115,6 +118,10 @@ constructor(
}
private fun listenForLockscreenToDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor.isAbleToDream
@@ -157,7 +164,10 @@ constructor(
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
- startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ startTransitionTo(
+ KeyguardState.PRIMARY_BOUNCER,
+ ownerReason = "#listenForLockscreenToPrimaryBouncer"
+ )
}
}
}
@@ -254,7 +264,8 @@ constructor(
transitionId =
startTransitionTo(
toState = KeyguardState.PRIMARY_BOUNCER,
- animator = null, // transition will be manually controlled
+ animator = null, // transition will be manually controlled,
+ ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
)
}
}
@@ -309,47 +320,51 @@ constructor(
}
}
- private fun listenForLockscreenToOccluded() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
- (isOccluded, keyguardState) ->
- if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
- startTransitionTo(KeyguardState.OCCLUDED)
- }
+ private fun listenForLockscreenToOccludedOrDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo
+ .filter { it.isOnTop }
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (taskInfo, startedState) ->
+ if (startedState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(
+ if (taskInfo.isDream()) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.OCCLUDED
+ }
+ )
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
}
}
}
private fun listenForLockscreenToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- val toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- val modeOnCanceled =
- if (
- toState == KeyguardState.AOD &&
- lastStartedStep.from == KeyguardState.AOD
- ) {
- TransitionModeOnCanceled.REVERSE
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = toState,
- modeOnCanceled = modeOnCanceled,
- )
+ listenForSleepTransition(
+ from = KeyguardState.LOCKSCREEN,
+ modeOnCanceledFromStartedStep = { startedStep ->
+ if (
+ startedStep.to == KeyguardState.AOD && startedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
}
}
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index efb604d5dadc..f10327e02240 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,17 +22,17 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,20 +45,23 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForOccludedToLockscreenOrHub()
listenForOccludedToDreaming()
- listenForOccludedToAodOrDozing()
+ listenForOccludedToAsleep()
listenForOccludedToGone()
listenForOccludedToAlternateBouncer()
listenForOccludedToPrimaryBouncer()
@@ -90,43 +93,72 @@ constructor(
}
private fun listenForOccludedToLockscreenOrHub() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded
- .sample(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
- // Occlusion signals come from the framework, and should interrupt any
- // existing transition
- if (
- !isOccluded &&
- isShowing &&
- lastStartedKeyguardState.to == KeyguardState.OCCLUDED
- ) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (startedState == KeyguardState.OCCLUDED) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal)
+ ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
}
}
private fun listenForOccludedToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // We don't think OCCLUDED to GONE is possible. You should always have to go via a
+ // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
+ // dismisses it, and even "extend unlock" doesn't unlock the device in the background.
+ // If we're wrong - sorry, add it back here.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
- combine(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
)
.collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
@@ -142,25 +174,12 @@ constructor(
}
}
- private fun listenForOccludedToAodOrDozing() {
- scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
- }
- }
- }
+ fun dismissToGone() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
+ private fun listenForOccludedToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.OCCLUDED) }
}
private fun listenForOccludedToAlternateBouncer() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index c5a28463bf7e..391dccc7f444 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -31,7 +31,6 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
@@ -42,6 +41,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -59,18 +59,21 @@ constructor(
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.PRIMARY_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForPrimaryBouncerToGone()
- listenForPrimaryBouncerToAodOrDozing()
+ listenForPrimaryBouncerToAsleep()
listenForPrimaryBouncerToLockscreenHubOrOccluded()
listenForPrimaryBouncerToDreamingLockscreenHosted()
listenForTransitionToCamera(scope, keyguardInteractor)
@@ -128,74 +131,88 @@ constructor(
}
private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- powerInteractor.isAwake,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isDreaming,
- keyguardInteractor.isActiveDreamLockscreenHosted,
- communalInteractor.isIdleOnCommunal,
- )
- .collect {
- (
- isBouncerShowing,
- isAwake,
- lastStartedTransitionStep,
- occluded,
- isDreaming,
- isActiveDreamLockscreenHosted,
- isIdleOnCommunal) ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAwake &&
- !isActiveDreamLockscreenHosted
- ) {
- val toState =
- if (occluded && !isDreaming) {
- KeyguardState.OCCLUDED
- } else if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else if (isDreaming) {
- KeyguardState.DREAMING
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(toState)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ startedKeyguardTransitionStep,
+ powerInteractor.isAwake,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal
+ )
+ .filter { (_, startedStep, _, _) ->
+ startedStep.to == KeyguardState.PRIMARY_BOUNCER
}
- }
- }
- }
-
- private fun listenForPrimaryBouncerToAodOrDozing() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- powerInteractor.isAsleep,
+ .collect {
+ (
+ isBouncerShowing,
+ _,
+ isAwake,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isBouncerShowing &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ powerInteractor.isAwake,
startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Triple
- ),
- ::toQuad
- )
- .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
- ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAsleep
- ) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect {
+ (
+ isBouncerShowing,
+ isAwake,
+ lastStartedTransitionStep,
+ occluded,
+ isDreaming,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !isBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (occluded && !isDreaming) {
+ KeyguardState.OCCLUDED
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else if (isDreaming) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
}
- }
+ }
}
}
+ private fun listenForPrimaryBouncerToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.PRIMARY_BOUNCER) }
+ }
+
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
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 5410b10a4b93..f321bd7e13a0 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
@@ -163,15 +163,18 @@ constructor(
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is dismissible or not. */
val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Keyguard can be clipped at the top as the shade is dragged */
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
new file mode 100644
index 000000000000..9aa2202b4100
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Logic related to keyguard occlusion. The keyguard is occluded when an activity with
+ * FLAG_SHOW_WHEN_LOCKED is on top of the activity task stack, with that activity displaying on top
+ * of ("occluding") the lockscreen UI. Common examples of this are Google Maps Navigation and the
+ * secure camera.
+ *
+ * This should usually be used only by keyguard internal classes. Most System UI use cases should
+ * use [KeyguardTransitionInteractor] to see if we're in [KeyguardState.OCCLUDED] instead.
+ */
+@SysUISingleton
+class KeyguardOcclusionInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ val repository: KeyguardOcclusionRepository,
+ val powerInteractor: PowerInteractor,
+ val transitionInteractor: KeyguardTransitionInteractor,
+ val keyguardInteractor: KeyguardInteractor,
+) {
+ val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
+
+ /**
+ * Whether a SHOW_WHEN_LOCKED activity is on top of the task stack. This does not necessarily
+ * mean we're OCCLUDED, as we could be GONE (unlocked), with an activity that can (but is not
+ * currently) displaying over the lockscreen.
+ *
+ * Transition interactors use this to determine when we should transition to the OCCLUDED state.
+ *
+ * Outside of the transition/occlusion interactors, you almost certainly don't want to use this.
+ * Instead, use KeyguardTransitionInteractor to figure out if we're in KeyguardState.OCCLUDED.
+ */
+ val isShowWhenLockedActivityOnTop = showWhenLockedActivityInfo.map { it.isOnTop }
+
+ /** Whether we should start a transition due to the power button launch gesture. */
+ fun shouldTransitionFromPowerButtonGesture(): Boolean {
+ // powerButtonLaunchGestureTriggered remains true while we're awake from a power button
+ // gesture. Check that we were asleep or transitioning to asleep before starting a
+ // transition, to ensure we don't transition while moving between, for example,
+ // *_BOUNCER -> LOCKSCREEN.
+ return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
+ KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ }
+
+ /**
+ * Whether the SHOW_WHEN_LOCKED activity was launched from the double tap power button gesture.
+ * This remains true while the activity is running and emits false once it is killed.
+ */
+ val showWhenLockedActivityLaunchedFromPowerGesture =
+ merge(
+ // Emit true when the power launch gesture is triggered, since this means a
+ // SHOW_WHEN_LOCKED activity will be launched from the gesture (unless we're
+ // currently
+ // GONE, in which case we're going back to GONE and launching the insecure camera).
+ powerInteractor.detailedWakefulness
+ .sample(transitionInteractor.currentKeyguardState, ::Pair)
+ .map { (wakefulness, currentKeyguardState) ->
+ wakefulness.powerButtonLaunchGestureTriggered &&
+ currentKeyguardState != KeyguardState.GONE
+ },
+ // Emit false once that activity goes away.
+ isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Whether launching an occluding activity will automatically dismiss keyguard. This happens if
+ * the keyguard is dismissable.
+ */
+ val occludingActivityWillDismissKeyguard =
+ keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
+ * on top).
+ *
+ * This signal arrives from WM when a SHOW_WHEN_LOCKED activity is started or killed - it is
+ * never set directly by System UI. While we might be the reason the activity was started
+ * (launching the camera from the power button gesture), we ultimately only receive this signal
+ * once that activity starts. It's up to us to start the appropriate keyguard transitions,
+ * because that activity is going to be visible (or not) regardless.
+ */
+ fun setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop: Boolean,
+ taskInfo: RunningTaskInfo? = null
+ ) {
+ repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index d81f1f14158c..c28e49db9d37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,6 +41,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val shadeInteractor: ShadeInteractor,
+ private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
fun start() {
@@ -91,6 +92,12 @@ constructor(
}
scope.launch {
+ keyguardInteractor.isKeyguardDismissible.collect {
+ logger.log(TAG, VERBOSE, "isKeyguardDismissable", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.isAbleToDream.collect {
logger.log(TAG, VERBOSE, "isAbleToDream", it)
}
@@ -125,5 +132,11 @@ constructor(
logger.log(TAG, VERBOSE, "onCameraLaunchDetected", it)
}
}
+
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo.collect {
+ logger.log(TAG, VERBOSE, "showWhenLockedActivityInfo", it)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 37b331cd8455..00902b419a97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -42,6 +43,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -56,11 +58,15 @@ class KeyguardTransitionInteractor
@Inject
constructor(
@Application val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
+ private val fromAlternateBouncerTransitionInteractor:
+ dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
+ private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -207,6 +213,12 @@ constructor(
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ /** Which keyguard state to use when the device goes to sleep. */
+ val asleepKeyguardState: StateFlow<KeyguardState> =
+ keyguardRepository.isAodAvailable
+ .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+ .stateIn(scope, SharingStarted.Eagerly, DOZING)
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -368,7 +380,10 @@ constructor(
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ ALTERNATE_BOUNCER ->
+ fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
+ DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
else ->
Log.e(
"KeyguardTransitionInteractor",
@@ -421,12 +436,17 @@ constructor(
fromStatePredicate: (KeyguardState) -> Boolean,
toStatePredicate: (KeyguardState) -> Boolean,
): Flow<Boolean> {
+ return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
+ }
+
+ fun isInTransitionWhere(
+ fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
+ ): Flow<Boolean> {
return repository.transitions
.filter { it.transitionState != TransitionState.CANCELED }
.mapLatest {
it.transitionState != TransitionState.FINISHED &&
- fromStatePredicate(it.from) &&
- toStatePredicate(it.to)
+ fromToStatePredicate(it.from, it.to)
}
.distinctUntilChanged()
}
@@ -447,4 +467,16 @@ constructor(
*/
fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
stateMatcher(finishedKeyguardState.replayCache.last())
+
+ fun getCurrentState(): KeyguardState {
+ return currentKeyguardState.replayCache.last()
+ }
+
+ fun getStartedState(): KeyguardState {
+ return startedKeyguardState.replayCache.last()
+ }
+
+ fun getFinishedState(): KeyguardState {
+ return finishedKeyguardState.replayCache.last()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 4d731eccd9bb..8905c9e752de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -43,7 +43,6 @@ constructor(
private val scrimLogger: ScrimLogger,
private val powerInteractor: PowerInteractor,
) {
-
init {
listenForStartedKeyguardTransitionStep()
}
@@ -52,9 +51,7 @@ constructor(
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(
- willBeRevealedInState(it.to),
- )
+ lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
}
}
}
@@ -89,25 +86,25 @@ constructor(
companion object {
- /**
- * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
- * state after the transition is complete. If false, scrim will be fully hidden.
- */
- private fun willBeRevealedInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> false
- KeyguardState.DOZING -> false
- KeyguardState.AOD -> false
- KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
- KeyguardState.GLANCEABLE_HUB -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> true
- KeyguardState.OCCLUDED -> true
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ private fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
+ }
}
- }
val TAG = LightRevealScrimInteractor::class.simpleName!!
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 3ccbdba6d58e..375df3e8f5f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -18,15 +18,20 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.Log
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -46,6 +51,8 @@ sealed class TransitionInteractor(
val transitionInteractor: KeyguardTransitionInteractor,
val mainDispatcher: CoroutineDispatcher,
val bgDispatcher: CoroutineDispatcher,
+ val powerInteractor: PowerInteractor,
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
val name = this::class.simpleName ?: "UnknownTransitionInteractor"
abstract val transitionRepository: KeyguardTransitionRepository
@@ -65,7 +72,11 @@ sealed class TransitionInteractor(
suspend fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+ // Even more information about why the owner started this transition, if this is a dangerous
+ // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in
+ // a bugreport.
+ ownerReason: String = "",
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.replayCache.last() &&
@@ -85,7 +96,7 @@ sealed class TransitionInteractor(
return withContext(mainDispatcher) {
transitionRepository.startTransition(
TransitionInfo(
- name,
+ name + if (ownerReason.isNotBlank()) "($ownerReason)" else "",
fromState,
toState,
animator,
@@ -95,24 +106,107 @@ sealed class TransitionInteractor(
}
}
+ /**
+ * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a
+ * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
+ * gesture cases. If so, start the transition.
+ *
+ * Returns true if a transition was started, false otherwise.
+ */
+ suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+ if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
+ if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
+ // If the current state is GONE when the launch gesture is triggered, it means we
+ // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
+ // second tap indicates that the user's intent was actually to launch the unlocked
+ // (insecure) camera, so we should transition back to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture while GONE"
+ )
+ } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) {
+ // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the
+ // keyguard is dismissable. The activity launch will dismiss the keyguard, so we
+ // should transition to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture on dismissable keyguard"
+ )
+ } else {
+ // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
+ // which means we will launch the secure camera, which OCCLUDES the keyguard.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "Power button gesture on lockscreen"
+ )
+ }
+
+ return true
+ } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) {
+ // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
+ // it's visible.
+ // TODO(b/307976454) - Centralize transition to DREAMING here.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "SHOW_WHEN_LOCKED activity on top"
+ )
+
+ return true
+ } else {
+ // No transition needed, let the interactor figure out where to go.
+ return false
+ }
+ }
+
+ /**
+ * Transition to the appropriate state when the device goes to sleep while in [from].
+ *
+ * We could also just use [fromState], but it's more readable in the From*TransitionInteractor
+ * if you're explicitly declaring which state you're listening from. If you passed in the wrong
+ * state, [startTransitionTo] would complain anyway.
+ */
+ suspend fun listenForSleepTransition(
+ from: KeyguardState,
+ modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ ) {
+ powerInteractor.isAsleep
+ .filter { isAsleep -> isAsleep }
+ .sample(startedKeyguardTransitionStep)
+ .filter { startedStep -> startedStep.to == from }
+ .map(modeOnCanceledFromStartedStep)
+ .collect { modeOnCanceled ->
+ startTransitionTo(
+ toState = transitionInteractor.asleepKeyguardState.value,
+ modeOnCanceled = modeOnCanceled,
+ ownerReason = "Sleep transition triggered"
+ )
+ }
+ }
+
/** This signal may come in before the occlusion signal, and can provide a custom transition */
fun listenForTransitionToCamera(
scope: CoroutineScope,
keyguardInteractor: KeyguardInteractor,
) {
- scope.launch {
- keyguardInteractor.onCameraLaunchDetected
- .sample(transitionInteractor.finishedKeyguardState)
- .collect { finishedKeyguardState ->
- // Other keyguard state transitions may trigger on the first power button push,
- // so use the last finishedKeyguardState to determine the overriding FROM state
- if (finishedKeyguardState == fromState) {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.onCameraLaunchDetected
+ .sample(transitionInteractor.finishedKeyguardState)
+ .collect { finishedKeyguardState ->
+ // Other keyguard state transitions may trigger on the first power button
+ // push,
+ // so use the last finishedKeyguardState to determine the overriding FROM
+ // state
+ if (finishedKeyguardState == fromState) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index a9eec18319c3..7e39a884a69e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -43,6 +43,10 @@ constructor(
to = KeyguardState.GONE,
)
+ /**
+ * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
+ * and unlock, and also during insecure camera launch (which is GONE -> AOD (canceled) -> GONE).
+ */
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 105a7ed52311..445575f7e55d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -16,12 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -37,5 +40,23 @@ constructor(
to = KeyguardState.OCCLUDED,
)
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to AOD, the second cancels that transition and starts AOD -> OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
new file mode 100644
index 000000000000..c0b11959cbd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down DOZING->OCCLUDED transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class DozingToOccludedTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to DOZING, the second cancels that transition and starts DOZING ->
+ * OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
+ override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1760b927a6cd..f848717c2170 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -73,8 +73,10 @@ constructor(
AlternateBouncerToGoneTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -170,8 +172,10 @@ constructor(
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 840b309aee39..26c63f31fa46 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -109,7 +109,7 @@ import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
@@ -223,7 +223,7 @@ public class MediaControlPanel {
protected int mUid = Process.INVALID_UID;
private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final FalsingManager mFalsingManager;
private MetadataAnimationHandler mMetadataAnimationHandler;
private ColorSchemeTransition mColorSchemeTransition;
@@ -304,7 +304,7 @@ public class MediaControlPanel {
MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel,
Lazy<MediaDataManager> lazyMediaDataManager,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
SystemClock systemClock,
@@ -324,7 +324,7 @@ public class MediaControlPanel {
mSeekBarViewModel = seekBarViewModel;
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
@@ -737,7 +737,7 @@ public class MediaControlPanel {
mPackageName, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
} else {
@@ -761,7 +761,7 @@ public class MediaControlPanel {
}
}
} else {
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 5d113a97c42f..452cb7e29f20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,7 +31,8 @@ constructor(
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String?): LocalMediaManager {
- return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
- .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
+ return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
+ LocalMediaManager(context, localBluetoothManager, this, packageName)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
deleted file mode 100644
index b6e39372e34c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ /dev/null
@@ -1,83 +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.media.dialog
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
-import android.view.View
-import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import javax.inject.Inject
-
-/**
- * Factory to create [MediaOutputBroadcastDialog] objects.
- */
-class MediaOutputBroadcastDialogFactory @Inject constructor(
- private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
- private val uiEventLogger: UiEventLogger,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
-) {
- var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
-
- /** Creates a [MediaOutputBroadcastDialog] for the given package. */
- fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- // Dismiss the previous dialog, if any.
- mediaOutputBroadcastDialog?.dismiss()
-
- val controller = MediaOutputController(context, packageName,
- mediaSessionManager, lbm, starter, notifCollection,
- dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags, userTracker)
- val dialog =
- MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
- mediaOutputBroadcastDialog = dialog
-
- // Show the dialog.
- if (view != null) {
- dialogTransitionAnimator.showFromView(dialog, view)
- } else {
- dialog.show()
- }
- }
-
- /** dismiss [MediaOutputBroadcastDialog] if exist. */
- fun dismiss() {
- mediaOutputBroadcastDialog?.dismiss()
- mediaOutputBroadcastDialog = null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
new file mode 100644
index 000000000000..54d175c6a110
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.media.dialog
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import javax.inject.Inject
+
+/** Manager to create and show a [MediaOutputBroadcastDialog]. */
+class MediaOutputBroadcastDialogManager
+@Inject
+constructor(
+ private val context: Context,
+ private val broadcastSender: BroadcastSender,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val mediaOutputControllerFactory: MediaOutputController.Factory
+) {
+ var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
+
+ /** Creates a [MediaOutputBroadcastDialog] for the given package. */
+ fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // Dismiss the previous dialog, if any.
+ mediaOutputBroadcastDialog?.dismiss()
+
+ val controller = mediaOutputControllerFactory.create(packageName)
+ val dialog =
+ MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+ mediaOutputBroadcastDialog = dialog
+
+ // Show the dialog.
+ if (view != null) {
+ dialogTransitionAnimator.showFromView(dialog, view)
+ } else {
+ dialog.show()
+ }
+ }
+
+ /** dismiss [MediaOutputBroadcastDialog] if exist. */
+ fun dismiss() {
+ mediaOutputBroadcastDialog?.dismiss()
+ mediaOutputBroadcastDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b3b7bceea0e0..adee7f2c86be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -64,6 +64,7 @@ import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -91,6 +92,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
@@ -105,8 +110,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-
/**
* Controller for media output dialog
*/
@@ -170,10 +173,13 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
ACTION_BROADCAST_INFO_ICON
}
- @Inject
- public MediaOutputController(@NonNull Context context, String packageName,
- MediaSessionManager mediaSessionManager, LocalBluetoothManager
- lbm, ActivityStarter starter,
+ @AssistedInject
+ public MediaOutputController(
+ Context context,
+ @Assisted String packageName,
+ MediaSessionManager mediaSessionManager,
+ @Nullable LocalBluetoothManager lbm,
+ ActivityStarter starter,
CommonNotifCollection notifCollection,
DialogTransitionAnimator dialogTransitionAnimator,
NearbyMediaDevicesManager nearbyMediaDevicesManager,
@@ -193,7 +199,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
+ InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -222,6 +228,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
R.dimen.media_output_dialog_selectable_margin_end);
}
+ @AssistedFactory
+ public interface Factory {
+ /** Construct a MediaOutputController */
+ MediaOutputController create(String packageName);
+ }
+
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 02be0c1a6c2d..e7816a40bb5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -16,43 +16,24 @@
package com.android.systemui.media.dialog
-import android.app.KeyguardManager
import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import javax.inject.Inject
-/** Factory to create [MediaOutputDialog] objects. */
-open class MediaOutputDialogFactory
+/** Manager to create and show a [MediaOutputDialog]. */
+open class MediaOutputDialogManager
@Inject
constructor(
private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
private val uiEventLogger: UiEventLogger,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
+ private val mediaOutputControllerFactory: MediaOutputController.Factory,
) {
companion object {
const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +41,8 @@ constructor(
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- createWithController(
+ open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ createAndShowWithController(
packageName,
aboveStatusBar,
controller =
@@ -78,12 +59,12 @@ constructor(
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun createWithController(
+ open fun createAndShowWithController(
packageName: String,
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
) {
- create(
+ createAndShow(
packageName,
aboveStatusBar,
dialogTransitionAnimatorController = controller,
@@ -91,8 +72,10 @@ constructor(
)
}
- open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
- create(
+ open fun createAndShowForSystemRouting(
+ controller: DialogTransitionAnimator.Controller? = null
+ ) {
+ createAndShow(
packageName = null,
aboveStatusBar = false,
dialogTransitionAnimatorController = null,
@@ -100,7 +83,7 @@ constructor(
)
}
- private fun create(
+ private fun createAndShow(
packageName: String?,
aboveStatusBar: Boolean,
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
@@ -109,23 +92,9 @@ constructor(
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller =
- MediaOutputController(
- context,
- packageName,
- mediaSessionManager,
- lbm,
- starter,
- notifCollection,
- dialogTransitionAnimator,
- nearbyMediaDevicesManager,
- audioManager,
- powerExemptionManager,
- keyGuardManager,
- featureFlags,
- userTracker
- )
- val dialog =
+ val controller = mediaOutputControllerFactory.create(packageName)
+
+ val mediaOutputDialog =
MediaOutputDialog(
context,
aboveStatusBar,
@@ -135,16 +104,15 @@ constructor(
uiEventLogger,
includePlaybackAndAppMetadata
)
- mediaOutputDialog = dialog
// Show the dialog.
if (dialogTransitionAnimatorController != null) {
dialogTransitionAnimator.show(
- dialog,
+ mediaOutputDialog,
dialogTransitionAnimatorController,
)
} else {
- dialog.show()
+ mediaOutputDialog.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 38d31ed92141..774792f5030d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -31,8 +31,8 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
* BroadcastReceiver for handling media output intent
*/
class MediaOutputDialogReceiver @Inject constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
+ private val mediaOutputBroadcastDialogManager: MediaOutputBroadcastDialogManager
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
@@ -42,7 +42,7 @@ class MediaOutputDialogReceiver @Inject constructor(
launchMediaOutputDialogIfPossible(packageName)
}
MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
- mediaOutputDialogFactory.createDialogForSystemRouting()
+ mediaOutputDialogManager.createAndShowForSystemRouting()
}
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
if (!legacyLeAudioSharing()) return
@@ -55,7 +55,7 @@ class MediaOutputDialogReceiver @Inject constructor(
private fun launchMediaOutputDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputDialogFactory.create(packageName, false)
+ mediaOutputDialogManager.createAndShow(packageName, false)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
}
@@ -63,7 +63,7 @@ class MediaOutputDialogReceiver @Inject constructor(
private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputBroadcastDialogFactory.create(
+ mediaOutputBroadcastDialogManager.createAndShow(
packageName, aboveStatusBar = true, view = null)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index b5b1f0ffe23d..6e7e0f241dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -34,15 +34,15 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue.
private static final String TAG = "MediaOutputSwitcherDialogUI";
private final CommandQueue mCommandQueue;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
@Inject
public MediaOutputSwitcherDialogUI(
Context context,
CommandQueue commandQueue,
- MediaOutputDialogFactory mediaOutputDialogFactory) {
+ MediaOutputDialogManager mediaOutputDialogManager) {
mCommandQueue = commandQueue;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
}
@Override
@@ -54,7 +54,7 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue.
@MainThread
public void showMediaOutputSwitcher(String packageName) {
if (!TextUtils.isEmpty(packageName)) {
- mMediaOutputDialogFactory.create(packageName, false, null);
+ mMediaOutputDialogManager.createAndShow(packageName, false, null);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c0e688f0f82f..c7aae3ca788e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -23,6 +23,7 @@ import android.app.Service
import android.app.role.RoleManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
@@ -37,20 +38,21 @@ import dagger.multibindings.IntoMap
interface NoteTaskModule {
@[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
- fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+ fun bindNoteTaskControllerUpdateService(service: NoteTaskControllerUpdateService): Service
- @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
- fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+ @[Binds IntoMap ClassKey(NoteTaskBubblesService::class)]
+ fun bindNoteTaskBubblesService(service: NoteTaskBubblesService): Service
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
- fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
+ fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity
@[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
- fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
- Activity
+ fun bindLaunchNotesRoleSettingsTrampolineActivity(
+ activity: LaunchNotesRoleSettingsTrampolineActivity
+ ): Activity
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
- fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
+ fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 2d63dbcb82fa..7d3f2a532aae 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -25,5 +25,7 @@ import dagger.multibindings.IntoSet
interface NoteTaskQuickAffordanceModule {
@[Binds IntoSet]
- fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+ fun bindNoteTaskQuickAffordance(
+ impl: NoteTaskQuickAffordanceConfig
+ ): KeyguardQuickAffordanceConfig
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index e1d1ec207938..5432793d8117 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -20,20 +20,24 @@ data class WakefulnessModel(
* the [KeyguardTransitionInteractor].
*/
internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
-
val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
- /**
+ /**
* Whether the power button double tap gesture was triggered since the last time went to sleep.
* If this value is true while [isAsleep]=true, it means we'll be waking back up shortly. If it
* is true while [isAwake]=true, it means we're awake because of the button gesture.
*
- * This value remains true until the next time [isAsleep]=true.
+ * This value remains true until the next time [isAsleep]=true, since it would otherwise be
+ * totally arbitrary at what point we decide the gesture was no longer "triggered". Since a
+ * sleep event is guaranteed to arrive prior to the next power button gesture (as the first tap
+ * of the double tap always begins a sleep transition), this will always be reset to false prior
+ * to a subsequent power gesture.
*/
val powerButtonLaunchGestureTriggered: Boolean = false,
) {
- fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE ||
+ fun isAwake() =
+ internalWakefulnessState == WakefulnessState.AWAKE ||
internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
fun isAsleep() = !isAwake()
@@ -48,11 +52,10 @@ data class WakefulnessModel(
fun isAsleepFrom(wakeSleepReason: WakeSleepReason) =
isAsleep() && lastSleepReason == wakeSleepReason
- fun isAwakeOrAsleepFrom(reason: WakeSleepReason) =
- isAsleepFrom(reason) || isAwakeFrom(reason)
+ fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = isAsleepFrom(reason) || isAwakeFrom(reason)
fun isAwakeFromTapOrGesture(): Boolean {
- return isAwake() && (lastWakeReason == WakeSleepReason.TAP ||
- lastWakeReason == WakeSleepReason.GESTURE)
+ return isAwake() &&
+ (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 000000000000..22bbbbb62019
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.battery.domain.interactor
+
+import android.os.UserHandle
+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.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<BatterySaverTileModel> =
+ combine(
+ batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+ batteryController
+ .isBatteryPowerSaveEnabled()
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext),
+ batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+ ) {
+ isPluggedIn: Boolean,
+ isPowerSaverEnabled: Boolean,
+ _, // we are only interested in battery level change, not the actual level
+ ->
+ BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 000000000000..1e4eb38008bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.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.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+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.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (!data.isPluggedIn) {
+ batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 000000000000..dbec50dacf0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+ val isPluggedIn: Boolean
+ val isPowerSaving: Boolean
+
+ /** For when the device does not support extreme battery saver mode. */
+ data class Standard(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ ) : BatterySaverTileModel
+
+ /**
+ * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+ * determined through [isExtremeSaving].
+ */
+ data class Extreme(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ val isExtremeSaving: Boolean,
+ ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 000000000000..0c08fbacfcfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.battery.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.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+ @Main protected val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+ override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.battery_detail_switch_title)
+ contentDescription = label
+
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+ else R.drawable.qs_battery_saver_icon_off,
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isPluggedIn) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ } else if (data.isPowerSaving) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+ if (data is BatterySaverTileModel.Extreme) {
+ secondaryLabel =
+ resources.getString(
+ if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+ else R.string.standard_battery_saver_text
+ )
+ stateDescription = secondaryLabel
+ } else {
+ secondaryLabel = ""
+ }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
new file mode 100644
index 000000000000..caae4d26634d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.internet.domain
+
+import android.content.Context
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [InternetTileModel] to [QSTileState]. */
+class InternetTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val context: Context,
+) : QSTileDataToStateMapper<InternetTileModel> {
+
+ override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_internet_label)
+ expandedAccessibilityClass = Switch::class
+
+ if (data.secondaryLabel != null) {
+ secondaryLabel = data.secondaryLabel.loadText(context)
+ } else {
+ secondaryLabel = data.secondaryTitle
+ }
+
+ stateDescription = data.stateDescription.loadContentDescription(context)
+ contentDescription = data.contentDescription.loadContentDescription(context)
+
+ if (data.icon != null) {
+ this.icon = { data.icon }
+ } else if (data.iconId != null) {
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(data.iconId!!, theme),
+ contentDescription = null
+ )
+ this.icon = { loadedIcon }
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+
+ activationState =
+ if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE
+ else QSTileState.ActivationState.INACTIVE
+
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
new file mode 100644
index 000000000000..fdc596b3030c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -0,0 +1,275 @@
+/*
+ * 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.internet.domain.interactor
+
+import android.annotation.StringRes
+import android.content.Context
+import android.os.UserHandle
+import android.text.Html
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+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.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+/** Observes internet state changes providing the [InternetTileModel]. */
+class InternetTileDataInteractor
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
+ private val connectivityRepository: ConnectivityRepository,
+ ethernetInteractor: EthernetInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
+ wifiInteractor: WifiInteractor,
+) : QSTileDataInteractor<InternetTileModel> {
+ private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
+
+ // Three symmetrical Flows that can be switched upon based on the value of
+ // [DefaultConnectionModel]
+ private val wifiIconFlow: Flow<InternetTileModel> =
+ wifiInteractor.wifiNetwork.flatMapLatest {
+ val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
+ if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+ val secondary = removeDoubleQuotes(it.ssid)
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+ stateDescription = wifiIcon.contentDescription,
+ contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
+ )
+ )
+ } else {
+ notConnectedFlow
+ }
+ }
+
+ private val mobileDataContentName: Flow<CharSequence?> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ flowOf(null)
+ } else {
+ combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+ val cd = loadString(networkTypeIconGroup.contentDescription)
+ if (isRoaming) {
+ val roaming = context.getString(R.string.data_connection_roaming)
+ if (cd != null) {
+ context.getString(R.string.mobile_data_text_format, roaming, cd)
+ } else {
+ roaming
+ }
+ } else {
+ cd
+ }
+ }
+ }
+ }
+
+ private val mobileIconFlow: Flow<InternetTileModel> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ combine(
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+
+ val stateLevel = signalIcon.level
+ val drawable = SignalDrawable(context)
+ drawable.setLevel(stateLevel)
+ val loadedIcon = Icon.Loaded(drawable, null)
+
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription = ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(context)
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun mobileDataContentConcat(
+ networkName: String?,
+ dataContentDescription: CharSequence?
+ ): CharSequence {
+ if (dataContentDescription == null) {
+ return networkName ?: ""
+ }
+ if (networkName == null) {
+ return Html.fromHtml(dataContentDescription.toString(), 0)
+ }
+
+ return Html.fromHtml(
+ context.getString(
+ R.string.mobile_carrier_text_format,
+ networkName,
+ dataContentDescription
+ ),
+ 0
+ )
+ }
+
+ private fun loadString(@StringRes resId: Int): CharSequence? =
+ if (resId != 0) {
+ context.getString(resId)
+ } else {
+ null
+ }
+
+ private val ethernetIconFlow: Flow<InternetTileModel> =
+ ethernetInteractor.icon.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ val secondary = it.contentDescription
+ flowOf(
+ InternetTileModel.Active(
+ secondaryLabel = secondary?.toText(),
+ iconId = it.res,
+ stateDescription = null,
+ contentDescription = secondary,
+ )
+ )
+ }
+ }
+
+ private val notConnectedFlow: StateFlow<InternetTileModel> =
+ combine(
+ wifiInteractor.areNetworksAvailable,
+ airplaneModeRepository.isAirplaneMode,
+ ) { networksAvailable, isAirplaneMode ->
+ when {
+ isAirplaneMode -> {
+ val secondary = context.getString(R.string.status_bar_airplane)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription = ContentDescription.Loaded(secondary),
+ )
+ }
+ networksAvailable -> {
+ val secondary =
+ context.getString(R.string.quick_settings_networks_available)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_available,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Loaded("$internetLabel,$secondary")
+ )
+ }
+ else -> {
+ NOT_CONNECTED_NETWORKS_UNAVAILABLE
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+ /**
+ * Consumable flow describing the correct state for the InternetTile.
+ *
+ * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+ * the interim providers (wifi, mobile, ethernet, or not-connected).
+ */
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<InternetTileModel> =
+ connectivityRepository.defaultConnections.flatMapLatest {
+ when {
+ it.ethernet.isDefault -> ethernetIconFlow
+ it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+ it.wifi.isDefault -> wifiIconFlow
+ else -> notConnectedFlow
+ }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+ private companion object {
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ fun removeDoubleQuotes(string: String?): String? {
+ if (string == null) return null
+ return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') {
+ string.substring(1, string.length - 1)
+ } else string
+ }
+
+ fun ContentDescription.toText(): Text =
+ when (this) {
+ is ContentDescription.Loaded -> Text.Loaded(this.description)
+ is ContentDescription.Resource -> Text.Resource(this.res)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
new file mode 100644
index 000000000000..2620cd555d8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
+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.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles internet tile clicks. */
+class InternetTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val mainContext: CoroutineContext,
+ private val internetDialogManager: InternetDialogManager,
+ private val accessPointController: AccessPointController,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<InternetTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ withContext(mainContext) {
+ internetDialogManager.create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ action.view,
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_WIFI_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
new file mode 100644
index 000000000000..ece904611782
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.model
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+ val secondaryTitle: CharSequence?
+ val secondaryLabel: Text?
+ val iconId: Int?
+ val icon: Icon?
+ val stateDescription: ContentDescription?
+ val contentDescription: ContentDescription?
+
+ data class Active(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+
+ data class Inactive(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index d0585d3782c2..20bd7c65c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -64,8 +64,6 @@ public class ScrimView extends View {
private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
- private Runnable mChangeRunnable;
- private Executor mChangeRunnableExecutor;
private Executor mExecutor;
private Looper mExecutorLooper;
@Nullable
@@ -270,9 +268,6 @@ public class ScrimView extends View {
mDrawable.invalidateSelf();
}
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
public int getTint() {
@@ -300,9 +295,6 @@ public class ScrimView extends View {
mViewAlpha = alpha;
mDrawable.setAlpha((int) (255 * alpha));
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
});
}
@@ -311,14 +303,6 @@ public class ScrimView extends View {
return mViewAlpha;
}
- /**
- * Sets a callback that is invoked whenever the alpha, color, or tint change.
- */
- public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
- mChangeRunnable = changeRunnable;
- mChangeRunnableExecutor = changeRunnableExecutor;
- }
-
@Override
protected boolean canReceivePointerEvents() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index ef4e5308e18e..a12b9709a063 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -84,6 +84,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -457,14 +458,43 @@ public final class KeyboardShortcutListSearch {
List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
new ArrayList<>();
for (KeyboardShortcutGroup group : keyboardShortcutGroups) {
- CharSequence categoryTitle = group.getLabel();
- List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+ KeyboardShortcutMultiMappingGroup mappedGroup =
+ new KeyboardShortcutMultiMappingGroup(
+ group.getLabel(),
+ new ArrayList<>());
+ Map<String, List<ShortcutMultiMappingInfo>> shortcutMap = new LinkedHashMap<>();
for (KeyboardShortcutInfo info : group.getItems()) {
- shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info));
+ String label = info.getLabel().toString();
+ Icon icon = info.getIcon();
+ if (shortcutMap.containsKey(label)) {
+ List<ShortcutMultiMappingInfo> shortcuts = shortcutMap.get(label);
+ boolean foundSameIcon = false;
+ for (ShortcutMultiMappingInfo shortcut : shortcuts) {
+ Icon shortcutIcon = shortcut.getIcon();
+ if ((shortcutIcon != null
+ && icon != null
+ && shortcutIcon.sameAs(icon))
+ || (shortcutIcon == null && icon == null)) {
+ foundSameIcon = true;
+ shortcut.addShortcutKeyGroup(new ShortcutKeyGroup(info, null));
+ break;
+ }
+ }
+ if (!foundSameIcon) {
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ }
+ } else {
+ List<ShortcutMultiMappingInfo> shortcuts = new ArrayList<>();
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ shortcutMap.put(label, shortcuts);
+ }
}
- keyboardShortcutMultiMappingGroups.add(
- new KeyboardShortcutMultiMappingGroup(
- categoryTitle, shortcutMultiMappingInfos));
+ for (List<ShortcutMultiMappingInfo> shortcutInfos : shortcutMap.values()) {
+ for (ShortcutMultiMappingInfo shortcutInfo : shortcutInfos) {
+ mappedGroup.addItem(shortcutInfo);
+ }
+ }
+ keyboardShortcutMultiMappingGroups.add(mappedGroup);
}
return keyboardShortcutMultiMappingGroups;
}
@@ -1377,7 +1407,9 @@ public final class KeyboardShortcutListSearch {
ShortcutMultiMappingInfo(KeyboardShortcutInfo info) {
mLabel = info.getLabel();
mIcon = info.getIcon();
- mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null));
+ mShortcutKeyGroups = new ArrayList<>(
+ Arrays.asList(new ShortcutKeyGroup(info, null))
+ );
}
CharSequence getLabel() {
@@ -1388,6 +1420,10 @@ public final class KeyboardShortcutListSearch {
return mIcon;
}
+ void addShortcutKeyGroup(ShortcutKeyGroup group) {
+ mShortcutKeyGroups.add(group);
+ }
+
List<ShortcutKeyGroup> getShortcutKeyGroups() {
return mShortcutKeyGroups;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4ee83497b368..81f644f8acbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -352,10 +352,6 @@ constructor(
/** Called by the touch helper when the drag down was aborted and should be reset. */
internal fun onDragDownReset() {
logger.logDragDownAborted()
- nsslController.setDimmed(
- /* dimmed= */ true,
- /* animate= */ true,
- )
nsslController.resetScrollPosition()
nsslController.resetCheckSnoozeLeavebehind()
setDragDownAmountAnimated(0f)
@@ -366,12 +362,7 @@ constructor(
*
* @param above whether they dragged above it
*/
- internal fun onCrossedThreshold(above: Boolean) {
- nsslController.setDimmed(
- /* dimmed= */ !above,
- /* animate= */ true,
- )
- }
+ internal fun onCrossedThreshold(above: Boolean) {}
/** Called by the touch helper when the drag down was started */
internal fun onDragDownStarted(startingChild: ExpandableView?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 642eaccc3c99..c4d9cbfcd55c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@ import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
@@ -90,6 +94,7 @@ interface ConnectivityModule {
const val AIRPLANE_MODE_TILE_SPEC = "airplane"
const val DATA_SAVER_TILE_SPEC = "saver"
+ const val INTERNET_TILE_SPEC = "internet"
/** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
@Provides
@@ -168,5 +173,36 @@ interface ConnectivityModule {
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(INTERNET_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_no_internet_available,
+ labelRes = R.string.quick_settings_internet_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject InternetTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileViewModel(
+ factory: QSTileViewModelFactory.Static<InternetTileModel>,
+ mapper: InternetTileMapper,
+ stateInteractor: InternetTileDataInteractor,
+ userActionInteractor: InternetTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(INTERNET_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
new file mode 100644
index 000000000000..1bebbfd34ff4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Whether to set the status bar keyguard view occluded or not, and whether to animate that change.
+ */
+data class OccludedState(
+ val occluded: Boolean,
+ val animate: Boolean = false,
+)
+
+/** Handles logic around calls to [StatusBarKeyguardViewManager] in legacy code. */
+@Deprecated("Will be removed once all of SBKVM's responsibilies are refactored.")
+@SysUISingleton
+class StatusBarKeyguardViewManagerInteractor
+@Inject
+constructor(
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ powerInteractor: PowerInteractor,
+) {
+ /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
+ private val occlusionStateFromStartedStep: Flow<OccludedState> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .sample(powerInteractor.detailedWakefulness, ::Pair)
+ .map { (startedStep, wakefulness) ->
+ val transitioningFromPowerButtonGesture =
+ KeyguardState.deviceIsAsleepInState(startedStep.from) &&
+ startedStep.to == KeyguardState.OCCLUDED &&
+ wakefulness.powerButtonLaunchGestureTriggered
+
+ if (
+ startedStep.to == KeyguardState.OCCLUDED && !transitioningFromPowerButtonGesture
+ ) {
+ // Set occluded upon STARTED, *unless* we're transitioning from the power
+ // button, in which case we're going to play an animation over the lockscreen UI
+ // and need to remain unoccluded until the transition finishes.
+ return@map OccludedState(occluded = true, animate = false)
+ }
+
+ if (
+ startedStep.from == KeyguardState.OCCLUDED &&
+ startedStep.to == KeyguardState.LOCKSCREEN
+ ) {
+ // Set unoccluded immediately ONLY if we're transitioning back to the lockscreen
+ // since we need the views visible to animate them back down. This is a special
+ // case due to the way unocclusion remote animations are run. We can remove this
+ // once the unocclude animation uses the return animation framework.
+ return@map OccludedState(occluded = false, animate = false)
+ }
+
+ // Otherwise, wait for the transition to FINISH to decide.
+ return@map null
+ }
+ .filterNotNull()
+
+ /** Occlusion state to apply whenever a keyguard transition is FINISHED. */
+ private val occlusionStateFromFinishedStep =
+ keyguardTransitionInteractor.finishedKeyguardTransitionStep
+ .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair)
+ .map { (finishedStep, showWhenLockedOnTop) ->
+ // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to
+ // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack,
+ // and we're in any state other than GONE. This is necessary, for example, when we
+ // transition from OCCLUDED to a bouncer state. Otherwise, we should not be
+ // occluded.
+ val occluded =
+ finishedStep.to == KeyguardState.OCCLUDED ||
+ (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE)
+ OccludedState(occluded = occluded, animate = false)
+ }
+
+ /** Occlusion state to apply to SKBVM's setOccluded call. */
+ val keyguardViewOcclusionState =
+ merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
+ .distinctUntilChangedBy {
+ // Don't switch 'animate' values mid-transition.
+ it.occluded
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index ea9df9af8cff..05e8717d0005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -275,15 +275,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
return getHeight();
}
- /**
- * Sets the notification as dimmed. The default implementation does nothing.
- *
- * @param dimmed Whether the notification should be dimmed.
- * @param fade Whether an animation should be played to change the state.
- */
- public void setDimmed(boolean dimmed, boolean fade) {
- }
-
public boolean isRemoved() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5235b0..b90aa107d617 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.row
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
+import javax.inject.Provider
interface NotifRemoteViewsFactoryContainer {
val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@ constructor(
featureFlags: FeatureFlags,
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
- optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+ optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+ notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@ constructor(
if (notifLinearlayoutOptimized()) {
add(optimizedLinearLayoutFactory)
}
+ if (NotificationViewFlipperPausing.isEnabled) {
+ add(notificationViewFlipperFactory.get())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index c17ee3991713..f835cca1a60c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,6 +42,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import com.android.app.tracing.TraceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
@@ -369,49 +370,55 @@ public class NotificationContentInflater implements NotificationRowContentBinder
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
NotificationContentInflaterLogger logger) {
- InflationProgress result = new InflationProgress();
- final NotificationEntry entryForLogging = row.getEntry();
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
- result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
- result.newExpandedView = createExpandedView(builder, isLowPriority);
- }
+ return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
+ InflationProgress result = new InflationProgress();
+ final NotificationEntry entryForLogging = row.getEntry();
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
+ result.newContentView = createContentView(builder, isLowPriority,
+ usesIncreasedHeight);
+ }
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
- result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
- }
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
+ result.newExpandedView = createExpandedView(builder, isLowPriority);
+ }
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
- result.newPublicView = builder.makePublicContentView(isLowPriority);
- }
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
+ result.newHeadsUpView = builder.createHeadsUpContentView(
+ usesIncreasedHeadsUpHeight);
+ }
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating group summary remote view");
- result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
+ result.newPublicView = builder.makePublicContentView(isLowPriority);
}
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating low-priority group summary remote view");
- result.mNewLowPriorityGroupHeaderView =
- builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating group summary remote view");
+ result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating low-priority group summary remote view");
+ result.mNewLowPriorityGroupHeaderView =
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ }
}
- }
- setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
- result.packageContext = packageContext;
- result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
- result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- true /* showingPublic */);
- return result;
+ setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
+ result.packageContext = packageContext;
+ result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
+ false /* showingPublic */);
+ result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
+ true /* showingPublic */);
+
+ return result;
+ });
}
private static void setNotifsViewsInflaterFactory(InflationProgress result,
@@ -445,6 +452,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
RemoteViews.InteractionHandler remoteViewClickHandler,
@Nullable InflationCallback callback,
NotificationContentInflaterLogger logger) {
+ Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
@@ -621,6 +630,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
cancellationSignal.setOnCancelListener(
() -> {
logger.logAsyncTaskProgress(entry, "apply cancelled");
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
runningInflations.values().forEach(CancellationSignal::cancel);
});
@@ -769,17 +779,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if (!requiresHeightCheck(entry)) {
return true;
}
- Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
- int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- int referenceWidth = resources.getDimensionPixelSize(
- R.dimen.notification_validation_reference_width);
- int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
- view.measure(widthSpec, heightSpec);
- int minHeight = resources.getDimensionPixelSize(
- R.dimen.notification_validation_minimum_allowed_height);
- boolean result = view.getMeasuredHeight() >= minHeight;
- Trace.endSection();
- return result;
+ return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int referenceWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_reference_width);
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
+ View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ int minHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_minimum_allowed_height);
+ return view.getMeasuredHeight() >= minHeight;
+ });
}
/**
@@ -966,6 +976,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
entry.headsUpStatusBarText = result.headsUpStatusBarText;
entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
if (endListener != null) {
endListener.onAsyncInflationFinished(entry);
}
@@ -1102,83 +1113,97 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
@Override
+ protected void onPreExecute() {
+ Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
+ @Override
protected InflationProgress doInBackground(Void... params) {
- try {
- final StatusBarNotification sbn = mEntry.getSbn();
- // Ensure the ApplicationInfo is updated before a builder is recovered.
- updateApplicationInfo(sbn);
- final Notification.Builder recoveredBuilder
- = Notification.Builder.recoverBuilder(mContext,
- sbn.getNotification());
-
- Context packageContext = sbn.getPackageContext(mContext);
- if (recoveredBuilder.usesTemplate()) {
- // For all of our templates, we want it to be RTL
- packageContext = new RtlEnabledContext(packageContext);
- }
- boolean isConversation = mEntry.getRanking().isConversation();
- Notification.MessagingStyle messagingStyle = null;
- if (isConversation) {
- messagingStyle = mConversationProcessor.processNotification(
- mEntry, recoveredBuilder, mLogger);
- }
- InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
- recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, packageContext, mRow,
- mNotifLayoutInflaterFactoryProvider, mLogger);
-
- mLogger.logAsyncTaskProgress(mEntry,
- "getting existing smart reply state (on wrong thread!)");
- InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
- mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
- InflationProgress result = inflateSmartReplyViews(
- /* result = */ inflationProgress,
- mReInflateFlags,
- mEntry,
- mContext,
- packageContext,
- previousSmartReplyState,
- mSmartRepliesInflater,
- mLogger);
+ return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
+ () -> {
+ try {
+ return doInBackgroundInternal();
+ } catch (Exception e) {
+ mError = e;
+ mLogger.logAsyncTaskException(mEntry, "inflating", e);
+ return null;
+ }
+ });
+ }
- if (AsyncHybridViewInflation.isEnabled()) {
- // Inflate the single-line content view's ViewModel and ViewHolder from the
- // background thread, the ViewHolder needs to be bind with ViewModel later from
- // the main thread.
- result.mInflatedSingleLineViewModel = SingleLineViewInflater
- .inflateSingleLineViewModel(
- mEntry.getSbn().getNotification(),
- messagingStyle,
- recoveredBuilder,
- mContext
- );
- result.mInflatedSingleLineViewHolder =
- SingleLineViewInflater.inflateSingleLineViewHolder(
- isConversation,
- mReInflateFlags,
- mEntry,
- mContext,
- mLogger
- );
- }
+ private InflationProgress doInBackgroundInternal() {
+ final StatusBarNotification sbn = mEntry.getSbn();
+ // Ensure the ApplicationInfo is updated before a builder is recovered.
+ updateApplicationInfo(sbn);
+ final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+ mContext, sbn.getNotification());
+
+ Context packageContext = sbn.getPackageContext(mContext);
+ if (recoveredBuilder.usesTemplate()) {
+ // For all of our templates, we want it to be RTL
+ packageContext = new RtlEnabledContext(packageContext);
+ }
+ boolean isConversation = mEntry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor.processNotification(
+ mEntry, recoveredBuilder, mLogger);
+ }
+ InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
+ recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
+ mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+ mNotifLayoutInflaterFactoryProvider, mLogger);
+
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting existing smart reply state (on wrong thread!)");
+ InflatedSmartReplyState previousSmartReplyState =
+ mRow.getExistingSmartReplyState();
+ mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
+ InflationProgress result = inflateSmartReplyViews(
+ /* result = */ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ previousSmartReplyState,
+ mSmartRepliesInflater,
+ mLogger);
+
+ if (AsyncHybridViewInflation.isEnabled()) {
+ // Inflate the single-line content view's ViewModel and ViewHolder from the
+ // background thread, the ViewHolder needs to be bind with ViewModel later from
+ // the main thread.
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
- mLogger.logAsyncTaskProgress(mEntry,
- "getting row image resolver (on wrong thread!)");
- final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
- // wait for image resolver to finish preloading
- mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
- imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting row image resolver (on wrong thread!)");
+ final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
+ // wait for image resolver to finish preloading
+ mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
+ imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
- return result;
- } catch (Exception e) {
- mError = e;
- mLogger.logAsyncTaskException(mEntry, "inflating", e);
- return null;
- }
+ return result;
}
@Override
protected void onPostExecute(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+
if (mError == null) {
// Logged in detail in apply.
mCancellationSignal = apply(
@@ -1197,6 +1222,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
}
+ @Override
+ protected void onCancelled(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
private void handleError(Exception e) {
mEntry.onInflationTaskFinished();
StatusBarNotification sbn = mEntry.getSbn();
@@ -1294,4 +1324,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
public abstract void setResultView(View v);
public abstract RemoteViews getRemoteView();
}
+
+ private static final String ASYNC_TASK_TRACE_METHOD =
+ "NotificationContentInflater.AsyncInflationTask";
+ private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 000000000000..0594c1227a44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+ private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ ViewFlipper::class.java.name,
+ ViewFlipper::class.java.simpleName ->
+ ViewFlipper(context, attrs).also { viewFlipper ->
+ NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+ }
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 000000000000..133d3e730eff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+ fun bindWhileAttached(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ): DisposableHandle {
+ if (viewFlipper.isAutoStart) {
+ // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+ return DisposableHandle {}
+ }
+ return viewFlipper.repeatWhenAttached {
+ lifecycleScope.launch { bind(viewFlipper, viewModel) }
+ }
+ }
+
+ suspend fun bind(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+ private fun ViewFlipper.setPaused(paused: Boolean) {
+ if (paused) {
+ stopFlipping()
+ } else if (isAutoStart) {
+ startFlipping()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 000000000000..7694e58296ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -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.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 000000000000..cea6a2b197be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationViewFlipperPausing()
+
+ /**
+ * 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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index c90aceef6934..ab2f664fee88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -61,7 +61,6 @@ public class AmbientState implements Dumpable {
*/
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private int mScrollY;
- private boolean mDimmed;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
private boolean mDozing;
@@ -344,14 +343,6 @@ public class AmbientState implements Dumpable {
this.mScrollY = Math.max(scrollY, 0);
}
- /**
- * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
- * translucent and everything is scaled back a bit.
- */
- public void setDimmed(boolean dimmed) {
- mDimmed = dimmed;
- }
-
/** While dozing, we draw as little as possible, assuming a black background */
public void setDozing(boolean dozing) {
mDozing = dozing;
@@ -375,12 +366,6 @@ public class AmbientState implements Dumpable {
mHideSensitive = hideSensitive;
}
- public boolean isDimmed() {
- // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
- // you'd see the difference to the pulsing notification
- return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
- }
-
public boolean isDozing() {
return mDozing;
}
@@ -768,7 +753,6 @@ public class AmbientState implements Dumpable {
pw.println("mHideSensitive=" + mHideSensitive);
pw.println("mShadeExpanded=" + mShadeExpanded);
pw.println("mClearAllInProgress=" + mClearAllInProgress);
- pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
pw.println("mPanelFullWidth=" + mIsSmallScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 5343cbf4f9fa..03a108287087 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -35,7 +35,6 @@ public class AnimationFilter {
boolean animateZ;
boolean animateHeight;
boolean animateTopInset;
- boolean animateDimmed;
boolean animateHideSensitive;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
@@ -83,11 +82,6 @@ public class AnimationFilter {
return this;
}
- public AnimationFilter animateDimmed() {
- animateDimmed = true;
- return this;
- }
-
public AnimationFilter animateHideSensitive() {
animateHideSensitive = true;
return this;
@@ -128,7 +122,6 @@ public class AnimationFilter {
animateZ |= filter.animateZ;
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
- animateDimmed |= filter.animateDimmed;
animateHideSensitive |= filter.animateHideSensitive;
hasDelays |= filter.hasDelays;
mAnimatedProperties.addAll(filter.mAnimatedProperties);
@@ -142,7 +135,6 @@ public class AnimationFilter {
animateZ = false;
animateHeight = false;
animateTopInset = false;
- animateDimmed = false;
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d0c5c82b50ee..d1e5ab04a630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -88,7 +88,6 @@ public class ExpandableViewState extends ViewState {
| ExpandableViewState.LOCATION_MAIN_AREA;
public int height;
- public boolean dimmed;
public boolean hideSensitive;
public boolean belowSpeedBump;
public boolean inShelf;
@@ -128,7 +127,6 @@ public class ExpandableViewState extends ViewState {
if (viewState instanceof ExpandableViewState) {
ExpandableViewState svs = (ExpandableViewState) viewState;
height = svs.height;
- dimmed = svs.dimmed;
hideSensitive = svs.hideSensitive;
belowSpeedBump = svs.belowSpeedBump;
clipTopAmount = svs.clipTopAmount;
@@ -155,9 +153,6 @@ public class ExpandableViewState extends ViewState {
expandableView.setActualHeight(newHeight, false /* notifyListeners */);
}
- // apply dimming
- expandableView.setDimmed(this.dimmed, false /* animate */);
-
// apply hiding sensitive
expandableView.setHideSensitive(
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
@@ -216,9 +211,6 @@ public class ExpandableViewState extends ViewState {
abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
}
- // start dimmed animation
- expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
-
// apply below the speed bump
if (!NotificationIconContainerRefactor.isEnabled()) {
expandableView.setBelowSpeedBump(this.belowSpeedBump);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index fa973001cec7..28f874da0c74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -795,7 +795,6 @@ public class NotificationChildrenContainer extends ViewGroup
} else {
childState.setZTranslation(0);
}
- childState.dimmed = parentState.dimmed;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
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 27db84f6715e..b47b18d4512d 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
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_UP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -29,10 +31,6 @@ import static com.android.systemui.util.DumpUtilsKt.visibilityString;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
@@ -79,7 +77,6 @@ import android.widget.ScrollView;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -163,18 +160,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
- /**
- * The distance in pixels between sections when the sections are directly adjacent (no visible
- * gap is drawn between them). In this case we don't want to round their corners.
- */
- private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
private boolean mKeyguardBypassEnabled;
private final ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private int mCurrentStackHeight = Integer.MAX_VALUE;
- private final Paint mBackgroundPaint = new Paint();
- private final boolean mShouldDrawNotificationBackground;
private boolean mHighPriorityBeforeSpeedBump;
private float mExpandedHeight;
@@ -263,7 +253,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
- private boolean mDimmedNeedsAnimation;
private boolean mHideSensitiveNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mGoToFullShadeNeedsAnimation;
@@ -350,40 +339,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
};
private final NotificationSection[] mSections;
- private boolean mAnimateNextBackgroundTop;
- private boolean mAnimateNextBackgroundBottom;
- private boolean mAnimateNextSectionBoundsChange;
- private @ColorInt int mBgColor;
- private float mDimAmount;
- private ValueAnimator mDimAnimator;
private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
- private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDimAnimator = null;
- }
- };
- private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setDimAmount((Float) animation.getAnimatedValue());
- }
- };
protected ViewGroup mQsHeader;
// Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
- private boolean mContinuousBackgroundUpdate;
private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
updateViewShadows();
return true;
};
- private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
- updateBackground();
- return true;
- };
private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -481,7 +445,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mHeadsUpAnimatingAway;
private int mStatusBarState;
private int mUpcomingStatusBarState;
- private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
@@ -581,7 +544,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
private boolean mDismissUsingRowTranslationX = true;
private ExpandableNotificationRow mTopHeadsUpRow;
- private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
@@ -595,7 +557,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSplitShadeStateController = splitShadeStateController;
updateSplitNotificationShade();
}
- private FeatureFlags mFeatureFlags;
+ private final FeatureFlags mFeatureFlags;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -667,8 +629,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSections = mSectionsManager.createSectionsForBuckets();
mAmbientState = Dependency.get(AmbientState.class);
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -680,16 +640,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
mStateAnimator = new StackStateAnimator(context, this);
- mShouldDrawNotificationBackground =
- res.getBoolean(R.bool.config_drawNotificationBackground);
setOutlineProvider(mOutlineProvider);
// We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
// that adds a bunch of complexity, and drawing nothing isn't *that* expensive.
- boolean willDraw = SceneContainerFlag.isEnabled()
- || mShouldDrawNotificationBackground || mDebugLines;
+ boolean willDraw = SceneContainerFlag.isEnabled() || mDebugLines;
setWillNotDraw(!willDraw);
- mBackgroundPaint.setAntiAlias(true);
if (mDebugLines) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -812,9 +768,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
void updateBgColor() {
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
- updateBackgroundDimming();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof ActivatableNotificationView activatableView) {
@@ -835,14 +788,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
protected void onDraw(Canvas canvas) {
onJustBeforeDraw();
- if (mShouldDrawNotificationBackground
- && (mSections[0].getCurrentBounds().top
- < mSections[mSections.length - 1].getCurrentBounds().bottom
- || mAmbientState.isDozing())) {
- drawBackground(canvas);
- } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
- drawHeadsUpBackground(canvas);
- }
if (mDebugLines) {
onDrawDebug(canvas);
@@ -930,150 +875,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return textY;
}
- private void drawBackground(Canvas canvas) {
- int lockScreenLeft = mSidePaddings;
- int lockScreenRight = getWidth() - mSidePaddings;
- int lockScreenTop = mSections[0].getCurrentBounds().top;
- int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
- int hiddenLeft = getWidth() / 2;
- int hiddenTop = mTopPadding;
-
- float yProgress = 1 - mInterpolatedHideAmount;
- float xProgress = mHideXInterpolator.getInterpolation(
- (1 - mLinearHideAmount) * mBackgroundXFactor);
-
- int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
- int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
- int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
- int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
- mBackgroundAnimationRect.set(
- left,
- top,
- right,
- bottom);
-
- int backgroundTopAnimationOffset = top - lockScreenTop;
- // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
- boolean anySectionHasVisibleChild = false;
- for (NotificationSection section : mSections) {
- if (section.needsBackground()) {
- anySectionHasVisibleChild = true;
- break;
- }
- }
- boolean shouldDrawBackground;
- if (mKeyguardBypassEnabled && onKeyguard()) {
- shouldDrawBackground = isPulseExpanding();
- } else {
- shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
- }
- if (shouldDrawBackground) {
- drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
- }
-
- updateClipping();
- }
-
- /**
- * Draws round rects for each background section.
- * <p>
- * We want to draw a round rect for each background section as defined by {@link #mSections}.
- * However, if two sections are directly adjacent with no gap between them (e.g. on the
- * lockscreen where the shelf can appear directly below the high priority section, or while
- * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
- * section), we don't want to round the adjacent corners.
- * <p>
- * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
- * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
- * This method tracks the top of each rect we need to draw, then iterates through the visible
- * sections. If a section is not adjacent to the previous section, we draw the previous rect
- * behind the sections we've accumulated up to that point, then start a new rect at the top of
- * the current section. When we're done iterating we will always have one rect left to draw.
- */
- private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
- int animationYOffset) {
- int backgroundRectTop = top;
- int lastSectionBottom =
- mSections[0].getCurrentBounds().bottom + animationYOffset;
- int currentLeft = left;
- int currentRight = right;
- boolean first = true;
- for (NotificationSection section : mSections) {
- if (!section.needsBackground()) {
- continue;
- }
- int sectionTop = section.getCurrentBounds().top + animationYOffset;
- int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
- int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
- // If sections are directly adjacent to each other, we don't want to draw them
- // as separate roundrects, as the rounded corners right next to each other look
- // bad.
- if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
- || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- backgroundRectTop = sectionTop;
- }
- currentLeft = ownLeft;
- currentRight = ownRight;
- lastSectionBottom =
- section.getCurrentBounds().bottom + animationYOffset;
- first = false;
- }
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
-
- private void drawHeadsUpBackground(Canvas canvas) {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
-
- float top = getHeight();
- float bottom = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE
- && child instanceof ExpandableNotificationRow row) {
- if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
- && row.getProvider().shouldShowGutsOnSnapOpen()) {
- top = Math.min(top, row.getTranslationY());
- bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
- }
- }
- }
-
- if (top < bottom) {
- canvas.drawRoundRect(
- left, top, right, bottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
- }
-
- void updateBackgroundDimming() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
- // Interpolate between semi-transparent notification panel background color
- // and white AOD separator.
- float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
- mLinearHideAmount);
- int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
-
- if (mCachedBackgroundColor != color) {
- mCachedBackgroundColor = color;
- mBackgroundPaint.setColor(color);
- invalidate();
- }
- }
-
private void reinitView() {
initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
}
@@ -1359,9 +1160,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void onPreDrawDuringAnimation() {
mShelf.updateAppearance();
- if (!mNeedsAnimation && !mChildrenUpdateRequested) {
- updateBackground();
- }
}
private void updateScrollStateForAddedChildren() {
@@ -2565,125 +2363,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
- private void updateBackground() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
-
- updateBackgroundBounds();
- if (didSectionBoundsChange()) {
- boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
- || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
- if (!isExpanded()) {
- abortBackgroundAnimators();
- animate = false;
- }
- if (animate) {
- startBackgroundAnimation();
- } else {
- for (NotificationSection section : mSections) {
- section.resetCurrentBounds();
- }
- invalidate();
- }
- } else {
- abortBackgroundAnimators();
- }
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
- }
-
- private void abortBackgroundAnimators() {
- for (NotificationSection section : mSections) {
- section.cancelAnimators();
- }
- }
-
- private boolean didSectionBoundsChange() {
- for (NotificationSection section : mSections) {
- if (section.didBoundsChange()) {
- return true;
- }
- }
- return false;
- }
-
- private boolean areSectionBoundsAnimating() {
- for (NotificationSection section : mSections) {
- if (section.areBoundsAnimating()) {
- return true;
- }
- }
- return false;
- }
-
- private void startBackgroundAnimation() {
- // TODO(kprevas): do we still need separate fields for top/bottom?
- // or can each section manage its own animation state?
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- NotificationSection lastVisibleSection = getLastVisibleSection();
- for (NotificationSection section : mSections) {
- section.startBackgroundAnimation(
- section == firstVisibleSection
- ? mAnimateNextBackgroundTop
- : mAnimateNextSectionBoundsChange,
- section == lastVisibleSection
- ? mAnimateNextBackgroundBottom
- : mAnimateNextSectionBoundsChange);
- }
- }
-
- /**
- * Update the background bounds to the new desired bounds
- */
- private void updateBackgroundBounds() {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
- for (NotificationSection section : mSections) {
- section.getBounds().left = left;
- section.getBounds().right = right;
- }
-
- if (!mIsExpanded) {
- for (NotificationSection section : mSections) {
- section.getBounds().top = 0;
- section.getBounds().bottom = 0;
- }
- return;
- }
- int minTopPosition;
- NotificationSection lastSection = getLastVisibleSection();
- boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
- if (!onKeyguard) {
- minTopPosition = (int) (mTopPadding + mStackTranslation);
- } else if (lastSection == null) {
- minTopPosition = mTopPadding;
- } else {
- // The first sections could be empty while there could still be elements in later
- // sections. The position of these first few sections is determined by the position of
- // the first visible section.
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
- false /* shiftPulsingWithFirst */);
- minTopPosition = firstVisibleSection.getBounds().top;
- }
- boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
- && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
- for (NotificationSection section : mSections) {
- int minBottomPosition = minTopPosition;
- if (section == lastSection) {
- // We need to make sure the section goes all the way to the shelf
- minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
- + mShelf.getIntrinsicHeight());
- }
- minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
- shiftPulsingWithFirst);
- shiftPulsingWithFirst = false;
- }
- }
-
private NotificationSection getFirstVisibleSection() {
for (NotificationSection section : mSections) {
if (section.getFirstVisibleChild() != null) {
@@ -3184,13 +2863,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSections, getChildrenWithBackground());
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != previousFirstChild;
- mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
- mAnimateNextSectionBoundsChange = sectionViewsChanged;
} else {
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
mAnimateBottomOnLayout = false;
@@ -3344,7 +3017,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
setAnimationRunning(true);
mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
mAnimationEvents.clear();
- updateBackground();
updateViewShadows();
} else {
applyCurrentState();
@@ -3359,7 +3031,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
generatePositionChangeEvents();
generateTopPaddingEvent();
generateActivateEvent();
- generateDimmedEvent();
generateHideSensitiveEvent();
generateGoToFullShadeEvent();
generateViewResizeEvent();
@@ -3577,14 +3248,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mEverythingNeedsAnimation = false;
}
- private void generateDimmedEvent() {
- if (mDimmedNeedsAnimation) {
- mAnimationEvents.add(
- new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
- }
- mDimmedNeedsAnimation = false;
- }
-
private void generateHideSensitiveEvent() {
if (mHideSensitiveNeedsAnimation) {
mAnimationEvents.add(
@@ -3645,7 +3308,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
- if (!mSendingTouchesToSceneFramework) {
+ int action = ev.getActionMasked();
+ boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+ if (mSendingTouchesToSceneFramework) {
+ mController.sendTouchToSceneFramework(ev);
+ } else if (!isUpOrCancel) {
// if this is the first touch being sent to the scene framework,
// convert it into a synthetic DOWN event.
mSendingTouchesToSceneFramework = true;
@@ -3653,14 +3320,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
downEvent.setAction(MotionEvent.ACTION_DOWN);
mController.sendTouchToSceneFramework(downEvent);
downEvent.recycle();
- } else {
- mController.sendTouchToSceneFramework(ev);
}
- if (
- ev.getActionMasked() == MotionEvent.ACTION_UP
- || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
- ) {
+ if (isUpOrCancel) {
setIsBeingDragged(false);
}
return false;
@@ -3817,7 +3479,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -3854,7 +3516,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
break;
- case MotionEvent.ACTION_CANCEL:
+ case ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
getScrollRange())) {
@@ -3963,7 +3625,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mTouchIsClick = false;
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
debugShadeLog("handleEmptySpaceClick: touch event propagated further");
@@ -4104,8 +3766,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
break;
}
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
+ case ACTION_CANCEL:
+ case ACTION_UP:
/* Release the drag */
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
@@ -4486,48 +4148,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAnimationFinishedRunnables.clear();
}
- /**
- * See {@link AmbientState#setDimmed}.
- */
- void setDimmed(boolean dimmed, boolean animate) {
- dimmed &= onKeyguard();
- mAmbientState.setDimmed(dimmed);
- if (animate && mAnimationsEnabled) {
- mDimmedNeedsAnimation = true;
- mNeedsAnimation = true;
- animateDimmed(dimmed);
- } else {
- setDimAmount(dimmed ? 1.0f : 0.0f);
- }
- requestChildrenUpdate();
- }
-
- @VisibleForTesting
- boolean isDimmed() {
- return mAmbientState.isDimmed();
- }
-
- private void setDimAmount(float dimAmount) {
- mDimAmount = dimAmount;
- updateBackgroundDimming();
- }
-
- private void animateDimmed(boolean dimmed) {
- if (mDimAnimator != null) {
- mDimAnimator.cancel();
- }
- float target = dimmed ? 1.0f : 0.0f;
- if (target == mDimAmount) {
- return;
- }
- mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
- mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
- mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mDimAnimator.addListener(mDimEndListener);
- mDimAnimator.addUpdateListener(mDimUpdateListener);
- mDimAnimator.start();
- }
-
void updateSensitiveness(boolean animate, boolean hideSensitive) {
if (hideSensitive != mAmbientState.isHideSensitive()) {
int childCount = getChildCount();
@@ -4564,7 +4184,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
runAnimationFinishedRunnables();
setAnimationRunning(false);
- updateBackground();
updateViewShadows();
}
@@ -4714,7 +4333,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
invalidateOutline();
}
updateAlgorithmHeightAndPadding();
- updateBackgroundDimming();
requestChildrenUpdate();
updateOwnTranslationZ();
}
@@ -4747,21 +4365,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
- private int getNotGoneIndex(View child) {
- int count = getChildCount();
- int notGoneIndex = 0;
- for (int i = 0; i < count; i++) {
- View v = getChildAt(i);
- if (child == v) {
- return notGoneIndex;
- }
- if (v.getVisibility() != View.GONE) {
- notGoneIndex++;
- }
- }
- return -1;
- }
-
/**
* Returns whether or not a History button is shown in the footer. If there is no footer, then
* this will return false.
@@ -5266,13 +4869,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
void onStatePostChange(boolean fromShadeLocked) {
boolean onKeyguard = onKeyguard();
- mAmbientState.setDimmed(onKeyguard);
-
if (mHeadsUpAppearanceController != null) {
mHeadsUpAppearanceController.onStateChanged();
}
- setDimmed(onKeyguard, fromShadeLocked);
setExpandingEnabled(!onKeyguard);
if (!FooterViewRefactor.isEnabled()) {
updateFooter();
@@ -5676,7 +5276,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
- updateContinuousBackgroundDrawing();
updateStackPosition();
requestChildrenUpdate();
}
@@ -5711,7 +5310,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
view.setTranslationY(wakeUplocation);
}
}
- mDimmedNeedsAnimation = true;
}
void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
@@ -5763,7 +5361,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
updateContinuousShadowDrawing();
- updateContinuousBackgroundDrawing();
requestChildrenUpdate();
}
@@ -5786,7 +5383,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* @param numHeadsUp the number of active alerting notifications.
*/
public void setNumHeadsUp(long numHeadsUp) {
- mNumHeadsUp = numHeadsUp;
mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
}
@@ -6160,19 +5756,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSpeedBumpIndexDirty = true;
}
- void updateContinuousBackgroundDrawing() {
- boolean continuousBackground = !mAmbientState.isFullyAwake()
- && mSwipeHelper.isSwiping();
- if (continuousBackground != mContinuousBackgroundUpdate) {
- mContinuousBackgroundUpdate = continuousBackground;
- if (continuousBackground) {
- getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
- } else {
- getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
- }
- }
- }
-
private void resetAllSwipeState() {
Trace.beginSection("NSSL.resetAllSwipeState()");
mSwipeHelper.resetTouchState();
@@ -6259,7 +5842,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ(),
// ANIMATION_TYPE_ACTIVATED_CHILD
@@ -6267,8 +5849,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
.animateZ(),
// ANIMATION_TYPE_DIMMED
- new AnimationFilter()
- .animateDimmed(),
+ new AnimationFilter(),
// ANIMATION_TYPE_CHANGE_POSITION
new AnimationFilter()
@@ -6283,7 +5864,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ()
.hasDelays(),
@@ -6339,7 +5919,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
- .animateDimmed()
.animateHideSensitive()
.animateHeight()
.animateTopInset()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7c138776d5a5..3bdd0e9920c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -875,8 +875,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
- mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
-
mLockscreenShadeTransitionController.setStackScroller(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
@@ -1743,13 +1741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
/**
- * Set the dimmed state for all of the notification views.
- */
- public void setDimmed(boolean dimmed, boolean animate) {
- mView.setDimmed(dimmed, animate);
- }
-
- /**
* @return the inset during the full shade transition, that needs to be added to the position
* of the quick settings edge. This is relevant for media, that is transitioning
* from the keyguard host to the quick settings one.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1ef9a8f3d7ec..9b1952ba63fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -369,13 +369,11 @@ public class StackScrollAlgorithm {
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
- boolean dimmed = ambientState.isDimmed();
boolean hideSensitive = ambientState.isHideSensitive();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- childViewState.dimmed = dimmed;
childViewState.hideSensitive = hideSensitive;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f5237938ebfa..78fc1471053d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -42,8 +42,10 @@ import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -99,7 +101,9 @@ constructor(
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -155,8 +159,8 @@ constructor(
.distinctUntilChanged()
.dumpWhileCollecting("isShadeLocked")
- private val shadeCollapseFadeInComplete = MutableStateFlow(false)
- .dumpValue("shadeCollapseFadeInComplete")
+ private val shadeCollapseFadeInComplete =
+ MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
@@ -187,9 +191,8 @@ constructor(
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(LOCKSCREEN)
- .onStart { emit(0f) }
- .map { it > 0 }
+ .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+ .onStart { emit(false) }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
@@ -362,16 +365,16 @@ constructor(
private val alphaWhenGoneAndShadeState: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.transitions
- .map { step -> step.to == GONE && step.transitionState == FINISHED }
- .distinctUntilChanged(),
- keyguardInteractor.statusBarState,
- ) { isGoneTransitionFinished, statusBarState ->
- if (isGoneTransitionFinished && statusBarState == SHADE) {
- emit(1f)
+ keyguardTransitionInteractor.transitions
+ .map { step -> step.to == GONE && step.transitionState == FINISHED }
+ .distinctUntilChanged(),
+ keyguardInteractor.statusBarState,
+ ) { isGoneTransitionFinished, statusBarState ->
+ if (isGoneTransitionFinished && statusBarState == SHADE) {
+ emit(1f)
+ }
}
- }
- .dumpWhileCollecting("alphaWhenGoneAndShadeState")
+ .dumpWhileCollecting("alphaWhenGoneAndShadeState")
fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
@@ -379,7 +382,9 @@ constructor(
merge(
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToLockscreenTransitionViewModel.notificationAlpha,
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
@@ -433,30 +438,35 @@ constructor(
* alpha sources.
*/
val glanceableHubAlpha: Flow<Float> =
- isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
- combineTransform(
- lockscreenToGlanceableHubRunning,
- glanceableHubToLockscreenRunning,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
- if (isOnGlanceableHubWithoutShade) {
- // Notifications should not be visible on the glanceable hub.
- // TODO(b/321075734): implement a way to actually set the notifications to gone
- // while on the hub instead of just adjusting alpha
- emit(0f)
- } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) {
- emit(alpha)
- } else {
- // Not on the hub and no transitions running, return full visibility so we don't
- // block the notifications from showing.
- emit(1f)
+ isOnGlanceableHubWithoutShade
+ .flatMapLatest { isOnGlanceableHubWithoutShade ->
+ combineTransform(
+ lockscreenToGlanceableHubRunning,
+ glanceableHubToLockscreenRunning,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
+ if (isOnGlanceableHubWithoutShade) {
+ // Notifications should not be visible on the glanceable hub.
+ // TODO(b/321075734): implement a way to actually set the notifications to
+ // gone
+ // while on the hub instead of just adjusting alpha
+ emit(0f)
+ } else if (
+ lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
+ ) {
+ emit(alpha)
+ } else {
+ // Not on the hub and no transitions running, return full visibility so we
+ // don't
+ // block the notifications from showing.
+ emit(1f)
+ }
}
}
- }
- .dumpWhileCollecting("glanceableHubAlpha")
+ .dumpWhileCollecting("glanceableHubAlpha")
/**
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
@@ -464,20 +474,20 @@ constructor(
*/
fun translationY(params: BurnInParameters): Flow<Float> {
return combine(
- aodBurnInViewModel.translationY(params).onStart { emit(0f) },
- isOnLockscreenWithoutShade,
- merge(
- keyguardInteractor.keyguardTranslationY,
- occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
- )
- ) { burnInY, isOnLockscreenWithoutShade, translationY ->
- if (isOnLockscreenWithoutShade) {
- burnInY + translationY
- } else {
- 0f
+ aodBurnInViewModel.translationY(params).onStart { emit(0f) },
+ isOnLockscreenWithoutShade,
+ merge(
+ keyguardInteractor.keyguardTranslationY,
+ occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+ )
+ ) { burnInY, isOnLockscreenWithoutShade, translationY ->
+ if (isOnLockscreenWithoutShade) {
+ burnInY + translationY
+ } else {
+ 0f
+ }
}
- }
- .dumpWhileCollecting("translationY")
+ .dumpWhileCollecting("translationY")
}
/**
@@ -486,10 +496,10 @@ constructor(
*/
val translationX: Flow<Float> =
merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
- glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
- )
- .dumpWhileCollecting("translationX")
+ lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ )
+ .dumpWhileCollecting("translationX")
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d2e36b88fd9d..088f8949525b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -207,8 +207,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private ScrimView mNotificationsScrim;
private ScrimView mScrimBehind;
- private Runnable mScrimBehindChangeRunnable;
-
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
@@ -415,11 +413,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
- if (mScrimBehindChangeRunnable != null) {
- mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
- mScrimBehindChangeRunnable = null;
- }
-
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
@@ -1542,16 +1535,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
}
- public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
- // TODO: remove this. This is necessary because of an order-of-operations limitation.
- // The fix is to move more of these class into @SysUISingleton.
- if (mScrimBehind == null) {
- mScrimBehindChangeRunnable = changeRunnable;
- } else {
- mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
- }
- }
-
private void updateThemeColors() {
if (mScrimBehind == null) return;
int background = Utils.getColorAttr(mScrimBehind.getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ee844345d2ec..ba89d4ac22cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -95,6 +95,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -351,8 +352,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
-
private final JavaAdapter mJavaAdapter;
+ private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -386,7 +387,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
SelectedUserInteractor selectedUserInteractor,
Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
JavaAdapter javaAdapter,
- Lazy<SceneInteractor> sceneInteractorLazy
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -421,6 +423,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mSurfaceBehindInteractor = surfaceBehindInteractor;
mJavaAdapter = javaAdapter;
mSceneInteractorLazy = sceneInteractorLazy;
+ mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -503,6 +506,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
lockscreenVis || animatingSurface
),
this::consumeShowStatusBarKeyguardView);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarKeyguardViewManagerInteractor.getKeyguardViewOcclusionState(),
+ (occlusionState) -> setOccluded(
+ occlusionState.getOccluded(), occlusionState.getAnimate()));
}
}
@@ -1453,6 +1461,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 0128eb762296..80ccd646f6be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -22,6 +22,24 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isPluggedIn) }
+}
+
fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
return conflatedCallbackFlow {
val batteryCallback =
@@ -35,3 +53,35 @@ fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
}
.onStart { emit(isPowerSave) }
}
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(level)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+ trySend(isExtreme)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
index 0d6c0f59b2d0..10cf08221fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -25,8 +25,11 @@ import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.app.tracing.TraceUtils;
import com.android.systemui.settings.UserTracker;
+import kotlin.Unit;
+
/**
* Used to interact with per-user Settings.Secure and Settings.System settings (but not
* Settings.Global, since those do not vary per-user)
@@ -123,8 +126,16 @@ public interface UserSettingsProxy extends SettingsProxy {
default void registerContentObserverForUser(
Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
int userHandle) {
- getContentResolver().registerContentObserver(
- uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+ TraceUtils.trace(
+ () -> {
+ // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
+ return "USP#registerObserver#[" + uri.toString() + "]";
+ }, () -> {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver,
+ getRealUserHandle(userHandle));
+ return Unit.INSTANCE;
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 404563087041..deec215865b2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -265,7 +265,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ConfigurationController mConfigurationController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
private final VolumeNavigator mVolumeNavigator;
@@ -316,7 +316,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -340,7 +340,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mAccessibilityMgr = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mCsdWarningDialogFactory = csdWarningDialogFactory;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
@@ -1199,7 +1199,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mSettingsIcon.setOnClickListener(v -> {
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- mMediaOutputDialogFactory.dismiss();
+ mMediaOutputDialogManager.dismiss();
mVolumeNavigator.openVolumePanel(
mVolumePanelNavigationInteractor.getVolumePanelRoute());
});
@@ -2082,6 +2082,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
+ // The animator can't keep up with the volume changes so haptics need to be
+ // triggered here. This happens when the volume keys are continuously pressed.
+ row.deliverOnProgressChangedHaptics(false, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2486,10 +2489,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (getActiveRow().equals(mRow)
&& mRow.slider.getVisibility() == VISIBLE
&& mRow.mHapticPlugin != null) {
- mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
- if (!fromUser) {
- // Consider a change from program as the volume key being continuously pressed
- mRow.mHapticPlugin.onKeyDown();
+ if (fromUser || mRow.animTargetProgress == progress) {
+ // Deliver user-generated slider changes immediately, or when the animation
+ // completes
+ mRow.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2574,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
/* progressInterpolatorFactor= */ 1f,
/* progressBasedDragMinScale= */ 0f,
/* progressBasedDragMaxScale= */ 0.2f,
- /* additionalVelocityMaxBump= */ 0.15f,
+ /* additionalVelocityMaxBump= */ 0.25f,
/* deltaMillisForDragInterval= */ 0f,
- /* deltaProgressForDragThreshold= */ 0.015f,
- /* numberOfLowTicks= */ 5,
- /* maxVelocityToScale= */ 300f,
+ /* deltaProgressForDragThreshold= */ 0.05f,
+ /* numberOfLowTicks= */ 4,
+ /* maxVelocityToScale= */ 200,
/* velocityAxis= */ MotionEvent.AXIS_Y,
/* upperBookendScale= */ 1f,
/* lowerBookendScale= */ 0.05f,
@@ -2642,6 +2645,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
void removeHaptics() {
slider.setOnTouchListener(null);
}
+
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+ if (!fromUser) {
+ // Consider a change from program as the volume key being continuously pressed
+ mHapticPlugin.onKeyDown();
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 64a5644bc452..1f87ec8ffe02 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -24,7 +24,7 @@ import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -100,7 +100,7 @@ public interface VolumeModule {
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -116,7 +116,7 @@ public interface VolumeModule {
accessibilityManagerWrapper,
deviceProvisionedController,
configurationController,
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
interactionJankMonitor,
volumePanelNavigationInteractor,
volumeNavigator,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 2ab59984d06f..cb16abe7e575 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -20,7 +20,7 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -30,20 +30,22 @@ import javax.inject.Inject
class MediaOutputActionsInteractor
@Inject
constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
) {
fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
when (session) {
is MediaDeviceSession.Active -> {
- mediaOutputDialogFactory.createWithController(
+ mediaOutputDialogManager.createAndShowWithController(
session.packageName,
false,
expandable.dialogController()
)
}
is MediaDeviceSession.Inactive -> {
- mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+ mediaOutputDialogManager.createAndShowForSystemRouting(
+ expandable.dialogController()
+ )
}
else -> {
/* do nothing */
@@ -56,7 +58,7 @@ constructor(
cuj =
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- MediaOutputDialogFactory.INTERACTION_JANK_TAG
+ MediaOutputDialogManager.INTERACTION_JANK_TAG
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index f07932c0de69..e3be3822fb94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.animation
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
+@FlakyTest(bugId = 302149604)
class AnimatorTestRuleOrderTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 043dcaa0d919..3c073d5e7a3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -193,6 +193,34 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modeEstimate_batteryPercentView_isNotNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNotNull()
+ // Make sure that it was added to the view hierarchy
+ assertThat(mBatteryMeterView.batteryPercentView.parent).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modePercent_batteryPercentView_isNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNull()
+ }
+
+ @Test
fun contentDescription_manyUpdates_alwaysUpdated() {
// BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 7a18477bffdd..a569ceec32b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -42,7 +42,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -77,7 +77,8 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase {
@Mock SysUiState mSysUiState;
@Mock
DialogTransitionAnimator mDialogTransitionAnimator;
- @Mock MediaOutputDialogFactory mMediaOutputDialogFactory;
+ @Mock
+ MediaOutputDialogManager mMediaOutputDialogManager;
private SystemUIDialog mDialog;
private TextView mTitle;
private TextView mSubTitle;
@@ -96,7 +97,7 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase {
mBroadcastDialogDelegate = new BroadcastDialogDelegate(
mContext,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mLocalBluetoothManager,
new UiEventLoggerFake(),
mFakeExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 489665cd130a..51828c91de4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -6,6 +6,7 @@ import android.app.WindowConfiguration
import android.graphics.Point
import android.graphics.Rect
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
@@ -15,6 +16,7 @@ import android.view.View
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -130,6 +132,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
* surface, or the user will see the wallpaper briefly as the app animates in.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun noSurfaceAnimation_ifWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
@@ -320,6 +323,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
* If we are not wake and unlocking, we expect the unlock animation to play normally.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
arrayOf(remoteTarget1, remoteTarget2),
@@ -358,6 +362,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() {
whenever(powerManager.isInteractive).thenReturn(false)
@@ -389,6 +394,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() {
whenever(powerManager.isInteractive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0957748c9938..0bd4cbec64dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1263,7 +1263,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
new file mode 100644
index 000000000000..7bef01a7a5ce
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+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 junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromAodTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromAodTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to AOD and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() =
+ testScope.runTest {
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking up from wake and unlock should not start any transitions, we'll wait for the
+ // dismiss call.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ underTest.dismissAod()
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
new file mode 100644
index 000000000000..258dbf3efbae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+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 junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDozingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDozingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
new file mode 100644
index 000000000000..f534ba5bc68c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDreamingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testDoesNotTransitionToOccluded_occludingActivityOnTop_whileStillDreaming() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ runCurrent()
+
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 6aebe365dc8c..c3e24d579491 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.ActivityManager
+import android.app.WindowConfiguration
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@ import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,6 +45,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -171,4 +175,49 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
assertThatRepository(transitionRepository).noTransitionsStarted()
}
+
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToOccluded_whenShowWhenLockedActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToDream_whenDreamActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
new file mode 100644
index 000000000000..d3c48483d100
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+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.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromOccludedTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromOccludedTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to OCCLUDED and set up PowerInteractor and the occlusion repository.
+ powerInteractor.setAwakeForTest()
+ runBlocking {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToLockscreen() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index f33a5c9484ee..7ee8963aaa15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -16,14 +16,23 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -31,20 +40,27 @@ import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
val testScope = kosmos.testScope
val selectedUserInteractor = kosmos.selectedUserInteractor
val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
@Test
fun testSurfaceBehindVisibility() =
@@ -193,4 +209,85 @@ class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
fail("surfaceBehindModel was unexpectedly null.")
}
}
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToLockscreen_whenBouncerHides() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() =
+ testScope.runTest {
+ underTest.start()
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_bouncerHide_occludingActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ // Shouldn't transition to OCCLUDED until the bouncer hides.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
new file mode 100644
index 000000000000..8a77ed2130a9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+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.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardOcclusionInteractor
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Test
+ fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ false,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ // Power button gesture was not triggered a second time, so this should remain
+ // false.
+ false,
+ )
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 92aad692ac52..95606ae81e5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,8 +21,8 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -40,7 +40,6 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
-import com.android.systemui.kosmos.testDispatcher
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
@@ -48,7 +47,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,30 +90,26 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
private var commandQueue = kosmos.fakeCommandQueue
private val shadeRepository = kosmos.fakeShadeRepository
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val transitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
- private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
- private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
- private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
- private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor
- private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
- private lateinit var fromAlternateBouncerTransitionInteractor:
- FromAlternateBouncerTransitionInteractor
+ private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
+ private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
+ private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
+ private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+ private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
+ private val fromAlternateBouncerTransitionInteractor =
+ kosmos.fromAlternateBouncerTransitionInteractor
private val fromPrimaryBouncerTransitionInteractor =
kosmos.fromPrimaryBouncerTransitionInteractor
- private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
- FromDreamingLockscreenHostedTransitionInteractor
- private lateinit var fromGlanceableHubTransitionInteractor:
- FromGlanceableHubTransitionInteractor
+ private val fromDreamingLockscreenHostedTransitionInteractor =
+ kosmos.fromDreamingLockscreenHostedTransitionInteractor
+ private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
private val communalInteractor = kosmos.communalInteractor
@Before
@@ -125,122 +119,21 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
featureFlags = FakeFeatureFlags()
- val glanceableHubTransitions =
- GlanceableHubTransitions(
- bgDispatcher = kosmos.testDispatcher,
- transitionInteractor = transitionInteractor,
- transitionRepository = transitionRepository,
- communalInteractor = communalInteractor
- )
-
fromLockscreenTransitionInteractor.start()
fromPrimaryBouncerTransitionInteractor.start()
-
- fromDreamingTransitionInteractor =
- FromDreamingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
- )
- .apply { start() }
-
- fromDreamingLockscreenHostedTransitionInteractor =
- FromDreamingLockscreenHostedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- )
- .apply { start() }
-
- fromAodTransitionInteractor =
- FromAodTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGoneTransitionInteractor =
- FromGoneTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromDozingTransitionInteractor =
- FromDozingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromOccludedTransitionInteractor =
- FromOccludedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromAlternateBouncerTransitionInteractor =
- FromAlternateBouncerTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- communalInteractor = communalInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGlanceableHubTransitionInteractor =
- FromGlanceableHubTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- glanceableHubTransitions = glanceableHubTransitions,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- mSetFlagsRule.disableFlags(
- FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ fromDreamingTransitionInteractor.start()
+ fromDreamingLockscreenHostedTransitionInteractor.start()
+ fromAodTransitionInteractor.start()
+ fromGoneTransitionInteractor.start()
+ fromDozingTransitionInteractor.start()
+ fromOccludedTransitionInteractor.start()
+ fromAlternateBouncerTransitionInteractor.start()
+ fromGlanceableHubTransitionInteractor.start()
}
@Test
@@ -257,7 +150,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.PRIMARY_BOUNCER,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncer)",
animatorAssertion = { it.isNotNull() }
)
@@ -282,7 +177,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -307,7 +202,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -389,7 +284,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -414,7 +309,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -778,7 +673,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -803,7 +698,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -1070,12 +965,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
@Test
fun primaryBouncerToAod() =
testScope.runTest {
+ // GIVEN aod available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
// GIVEN a prior transition has run to PRIMARY_BOUNCER
bouncerRepository.setPrimaryShow(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- // GIVEN aod available and starting to sleep
- keyguardRepository.setAodAvailable(true)
powerInteractor.setAsleepForTest()
// WHEN the primaryBouncer stops showing
@@ -1085,7 +982,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition to AOD should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.AOD,
animatorAssertion = { it.isNotNull() },
@@ -1112,7 +1010,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition to DOZING should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.DOZING,
animatorAssertion = { it.isNotNull() },
@@ -1642,7 +1541,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncerDragging)",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
animatorAssertion = { it.isNull() }, // dragging should be manually animated
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2f92afa39b52..83e4d3130b67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -84,7 +84,7 @@ import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
@@ -160,7 +160,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet
- @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+ @Mock private lateinit var mediaOutputDialogManager: MediaOutputDialogManager
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var transitionParent: ViewGroup
@@ -266,7 +266,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
mediaViewController,
seekBarViewModel,
Lazy { mediaDataManager },
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
mediaCarouselController,
falsingManager,
clock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 087988469a49..83def8e47651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -42,16 +42,16 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
- private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
- mock(MediaOutputDialogFactory.class);
+ private final MediaOutputDialogManager mMockMediaOutputDialogManager =
+ mock(MediaOutputDialogManager.class);
- private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
- mock(MediaOutputBroadcastDialogFactory.class);
+ private final MediaOutputBroadcastDialogManager mMockMediaOutputBroadcastDialogManager =
+ mock(MediaOutputBroadcastDialogManager.class);
@Before
public void setup() {
- mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
- mMockMediaOutputBroadcastDialogFactory);
+ mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogManager,
+ mMockMediaOutputBroadcastDialogManager);
}
@Test
@@ -60,9 +60,10 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, times(1))
- .create(getContext().getPackageName(), false, null);
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), false, null);
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -71,8 +72,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -80,8 +82,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -92,8 +95,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -104,9 +108,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, times(1))
- .create(getContext().getPackageName(), true, null);
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), true, null);
}
@Test
@@ -117,8 +121,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -128,8 +133,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -139,8 +145,9 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -148,7 +155,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase {
Intent intent = new Intent("UnKnown Action");
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index dbfab64b004a..bda0e1ed5c46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,25 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
- private val fakeActivityTaskManager = FakeActivityTaskManager()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val repo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
+ private val kosmos = taskSwitcherKosmos()
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val testScope = kosmos.testScope
+ private val repo = kosmos.activityTaskManagerTasksRepository
@Test
fun launchRecentTask_taskIsMovedToForeground() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index fdd434acdc9f..6043ede66b31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,50 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
import android.os.Binder
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
- private val fakeActivityTaskManager = FakeActivityTaskManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper
- )
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+
+ private val repo = kosmos.mediaProjectionManagerRepository
@Test
fun switchProjectedTask_stateIsUpdatedWithNewTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index dfb688bbde4b..33e65f26716a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,55 +17,33 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitchInteractorTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val interactor = kosmos.taskSwitcherInteractor
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index c4e939339fa1..9382c5882b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,26 +18,22 @@ package com.android.systemui.mediaprojection.taskswitcher.ui
import android.app.Notification
import android.app.NotificationManager
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,39 +42,16 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private val notificationManager = mock<NotificationManager>()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
private lateinit var coordinator: TaskSwitcherNotificationCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 687f97082ce4..a468953b971e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,64 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
- private val scheduler = TestCoroutineScheduler()
- private val dispatcher = UnconfinedTestDispatcher(scheduler)
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
@Test
fun uiState_notProjecting_emitsNotShowing() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index a59ba071d7e8..eb692eb1b4ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -32,9 +32,9 @@ import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +51,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -65,7 +66,7 @@ public class DynamicChildBindControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mDependency.injectMockDependency(MediaOutputDialogManager.class);
allowTestableLooperAsMainThread();
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index b3fc25c47912..24195fe0640c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
@@ -238,6 +240,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
testComponent.runTest {
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fb49499fc29d..718f99841292 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -58,7 +58,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -159,7 +159,7 @@ public class NotificationTestHelper {
dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
- dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ dependency.injectMockDependency(MediaOutputDialogManager.class);
mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mKeyguardBypassController = mock(KeyguardBypassController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 000000000000..f88bd7ec60bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ val underTest
+ get() = kosmos.notificationViewFlipperViewModel
+
+ @Test
+ fun testIsPaused_falseWhenViewingShade() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN view flippers should NOT be paused
+ assertThat(isPaused).isFalse()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenViewingKeyguard() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN on keyguard
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenStartingToSleep() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ // AND device is starting to go to sleep
+ kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 3d752880f423..4715b33aa40a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -57,39 +57,6 @@ class AmbientStateTest : SysuiTestCase() {
)
}
- // region isDimmed
- @Test
- fun isDimmed_whenTrue_shouldReturnTrue() {
- sut.arrangeDimmed(true)
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenFalse_shouldReturnFalse() {
- sut.arrangeDimmed(false)
-
- assertThat(sut.isDimmed).isFalse()
- }
-
- @Test
- fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.dozeAmount = 0f
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.arrangePulseExpanding(false)
- sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount
-
- assertThat(sut.isDimmed).isTrue()
- }
- // endregion
-
// region pulseHeight
@Test
fun pulseHeight_whenValueChanged_shouldCallListener() {
@@ -383,12 +350,6 @@ class AmbientStateTest : SysuiTestCase() {
}
// region Arrange helper methods.
-private fun AmbientState.arrangeDimmed(value: Boolean) {
- isDimmed = value
- dozeAmount = if (value) 0f else 1f
- arrangePulseExpanding(!value)
-}
-
private fun AmbientState.arrangePulseExpanding(value: Boolean) {
if (value) {
dozeAmount = 1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index f326ceaf1950..220305cc6bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -38,7 +38,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -73,11 +72,11 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -97,13 +96,11 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -232,6 +229,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testUpdateStackHeight_qsExpansionZero() {
final float expansionFraction = 0.2f;
final float overExpansion = 50f;
@@ -307,14 +305,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- public void testNotDimmedOnKeyguard() {
- when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
- mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
- mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
- assertFalse(mStackScroller.isDimmed());
- }
-
- @Test
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
@@ -738,6 +728,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_noOffset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -754,6 +745,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_Offset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -773,12 +765,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void setFractionToShade_recomputesStackHeight() {
mStackScroller.setFractionToShade(1f);
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
// Given: shade is not closing, scrollY is 0
mAmbientState.setScrollY(0);
@@ -877,6 +871,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSplitShade_hasTopOverscroll() {
mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -949,6 +944,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetMaxDisplayedNotifications_notifiesListeners() {
ExpandableView.OnHeightChangedListener listener =
mock(ExpandableView.OnHeightChangedListener.class);
@@ -963,9 +959,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
public void testDispatchTouchEvent_sceneContainerDisabled() {
- Assume.assumeFalse(SceneContainerFlag.isEnabled());
-
MotionEvent event = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
@@ -981,34 +976,60 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @EnableSceneContainer
public void testDispatchTouchEvent_sceneContainerEnabled() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
mStackScroller.setIsBeingDragged(true);
- MotionEvent moveEvent = MotionEvent.obtain(
- SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
+ long downTime = SystemClock.uptimeMillis() - 100;
+ MotionEvent moveEvent1 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE,
- 0,
- 0,
+ 101,
+ 201,
0
);
- MotionEvent syntheticDownEvent = moveEvent.copy();
+ MotionEvent syntheticDownEvent = moveEvent1.copy();
syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ mStackScroller.dispatchTouchEvent(moveEvent1);
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ MotionEvent moveEvent2 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 102,
+ 202,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(moveEvent2);
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 103,
+ 203,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent);
+ assertFalse(mStackScroller.getIsBeingDragged());
}
@Test
- @EnableFlags(FLAG_SCENE_CONTAINER)
- public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ @EnableSceneContainer
+ public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() {
mStackScroller.setIsBeingDragged(true);
MotionEvent upEvent = MotionEvent.obtain(
@@ -1019,21 +1040,18 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
0,
0
);
- MotionEvent syntheticDownEvent = upEvent.copy();
- syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
-
- mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(upEvent)));
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
assertFalse(mStackScroller.getIsBeingDragged());
}
+ private MotionEvent captureTouchSentToSceneFramework() {
+ ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
+ return captor.getValue();
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1081,20 +1099,23 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
);
}
- private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
- private final MotionEvent mLeftEvent;
+ private MotionEventSubject assertThatMotionEvent(MotionEvent actual) {
+ return new MotionEventSubject(actual);
+ }
+
+ private static class MotionEventSubject {
+ private final MotionEvent mActual;
- MotionEventMatcher(MotionEvent leftEvent) {
- mLeftEvent = leftEvent;
+ MotionEventSubject(MotionEvent actual) {
+ mActual = actual;
}
- @Override
- public boolean matches(MotionEvent right) {
- return mLeftEvent.getActionMasked() == right.getActionMasked()
- && mLeftEvent.getDownTime() == right.getDownTime()
- && mLeftEvent.getEventTime() == right.getEventTime()
- && mLeftEvent.getX() == right.getX()
- && mLeftEvent.getY() == right.getY();
+ public void matches(MotionEvent expected) {
+ assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked());
+ assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime());
+ assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime());
+ assertThat(mActual.getX()).isEqualTo(expected.getX());
+ assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f050857d5df2..562aa6a4f497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -95,6 +95,7 @@ import com.android.systemui.shade.ShadeLockscreenInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -226,7 +227,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class)) {
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -736,7 +738,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class)) {
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3a6324d3de53..d0261ae7d256 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -69,7 +69,7 @@ import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.res.R;
@@ -130,7 +130,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Mock
DeviceProvisionedController mDeviceProvisionedController;
@Mock
- MediaOutputDialogFactory mMediaOutputDialogFactory;
+ MediaOutputDialogManager mMediaOutputDialogManager;
@Mock
InteractionJankMonitor mInteractionJankMonitor;
@Mock
@@ -196,7 +196,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mAccessibilityMgr,
mDeviceProvisionedController,
mConfigurationController,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mInteractionJankMonitor,
mVolumePanelNavigationInteractor,
mVolumeNavigator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index c3f677e8d0d3..d5411ad77ce4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
@@ -40,5 +41,6 @@ val Kosmos.occludingAppDeviceEntryInteractor by
context = mockedContext,
activityStarter = activityStarter,
powerInteractor = powerInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a9a2d91c0815..dcbd5777489a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.annotation.FloatRange
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -65,55 +66,79 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScope: TestScope,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
- sendTransitionSteps(from, to, testScope.testScheduler)
+ sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScheduler: TestCoroutineScheduler,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = from,
- to = to,
- value = 0f,
- )
+ step =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = from,
+ to = to,
+ value = 0f,
+ )
)
testScheduler.runCurrent()
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = from,
- to = to,
- value = 0.5f
+ if (
+ throughTransitionState == TransitionState.RUNNING ||
+ throughTransitionState == TransitionState.FINISHED
+ ) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = from,
+ to = to,
+ value = 0.5f
+ )
)
- )
- testScheduler.runCurrent()
+ testScheduler.runCurrent()
+ }
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = from,
- to = to,
- value = 1f,
+ if (throughTransitionState == TransitionState.FINISHED) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = from,
+ to = to,
+ value = 1f,
+ )
)
+ testScheduler.runCurrent()
+ }
+ }
+
+ suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ this.sendTransitionStep(
+ step = step,
+ validateStep = validateStep,
+ ownerName = step.ownerName
)
- testScheduler.runCurrent()
}
/**
@@ -132,7 +157,22 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio
* If you're testing something involving transitions themselves and are sure you want to send
* only a FINISHED step, override [validateStep].
*/
- suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ suspend fun sendTransitionStep(
+ from: KeyguardState = KeyguardState.OFF,
+ to: KeyguardState = KeyguardState.OFF,
+ value: Float = 0f,
+ transitionState: TransitionState = TransitionState.FINISHED,
+ ownerName: String = "",
+ step: TransitionStep =
+ TransitionStep(
+ from = from,
+ to = to,
+ value = value,
+ transitionState = transitionState,
+ ownerName = ownerName
+ ),
+ validateStep: Boolean = true
+ ) {
_transitions.replayCache.last().let { lastStep ->
if (
validateStep &&
@@ -159,7 +199,9 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio
step: TransitionStep,
validateStep: Boolean = true
): Job {
- return coroutineScope.launch { sendTransitionStep(step, validateStep) }
+ return coroutineScope.launch {
+ sendTransitionStep(step = step, validateStep = validateStep)
+ }
}
suspend fun sendTransitionSteps(
@@ -168,12 +210,13 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio
validateStep: Boolean = true
) {
steps.forEach {
- sendTransitionStep(it, validateStep = validateStep)
+ sendTransitionStep(step = it, validateStep = validateStep)
testScope.testScheduler.runCurrent()
}
}
override fun startTransition(info: TransitionInfo): UUID? {
+ Log.i("TEST", "Start transition: ", Exception())
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
new file mode 100644
index 000000000000..4c8bf9054106
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardOcclusionRepository by Kosmos.Fixture { KeyguardOcclusionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..530cbedbdd0c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromAlternateBouncerTransitionInteractor by
+ Kosmos.Fixture {
+ FromAlternateBouncerTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index 2477415cc06e..bbe37c18dd08 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -18,19 +18,21 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromAodTransitionInteractor by
Kosmos.Fixture {
FromAodTransitionInteractor(
transitionRepository = fakeKeyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
- scope = testScope,
+ scope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..23dcd965c028
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDozingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDozingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..f7a9d59eac26
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingLockscreenHostedTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingLockscreenHostedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..135644cfac3e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..1695327d75bc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromGlanceableHubTransitionInteractor by
+ Kosmos.Fixture {
+ FromGlanceableHubTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 25fc67a9691b..604d9e435e8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -17,11 +17,13 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromGoneTransitionInteractor by
Kosmos.Fixture {
@@ -34,5 +36,7 @@ val Kosmos.fromGoneTransitionInteractor by
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ biometricSettingsRepository = biometricSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b52676fc0ef..162fd9029bb0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
var Kosmos.fromLockscreenTransitionInteractor by
Kosmos.Fixture {
@@ -38,5 +39,6 @@ var Kosmos.fromLockscreenTransitionInteractor by
powerInteractor = powerInteractor,
glanceableHubTransitions = glanceableHubTransitions,
swipeToDismissInteractor = swipeToDismissInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..fc740a180dc4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromOccludedTransitionInteractor by
+ Kosmos.Fixture {
+ FromOccludedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 6b764491f32a..98babffb50d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
var Kosmos.fromPrimaryBouncerTransitionInteractor by
@@ -40,5 +41,6 @@ var Kosmos.fromPrimaryBouncerTransitionInteractor by
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6df7493be200..6cc1e8eba73d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,19 +16,21 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import dagger.Lazy
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
- fromPrimaryBouncerTransitionInteractor =
- Lazy { fromPrimaryBouncerTransitionInteractor },
- fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
+ keyguardRepository = keyguardRepository,
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = { fromAodTransitionInteractor },
+ fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
+ fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..8162520e5d88
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToOccludedTransitionViewModel by
+ Kosmos.Fixture {
+ DozingToOccludedTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index c2300a1ed1ad..75e3ac24e381 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.keyguard.ui.viewmodel
@@ -41,8 +40,10 @@ val Kosmos.keyguardRootViewModel by Fixture {
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index e1b1966aed6c..e78866904dfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.media
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.util.mockito.mock
-var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
+var Kosmos.mediaOutputDialogManager: MediaOutputDialogManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 920e5ee94cca..41d2d600e627 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.app.ActivityManager.RunningTaskInfo
import android.app.IActivityTaskManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 28393e837b93..2b6032ccafe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 000000000000..d344b75c9eb7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+ Kosmos.Fixture {
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher
+ )
+ }
+
+val Kosmos.mediaProjectionManagerRepository by
+ Kosmos.Fixture {
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = applicationCoroutineScope,
+ tasksRepository = activityTaskManagerTasksRepository,
+ backgroundDispatcher = testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ )
+ }
+
+val Kosmos.taskSwitcherInteractor by
+ Kosmos.Fixture {
+ TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+ }
+
+val Kosmos.taskSwitcherViewModel by
+ Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
new file mode 100644
index 000000000000..a2d1d93bbecb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.battery
+
+import com.android.systemui.battery.BatterySaverModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsBatterySaverTileConfig by
+ Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
new file mode 100644
index 000000000000..6772ba317da4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.internet
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsInternetTileConfig by
+ Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
new file mode 100644
index 000000000000..d79374021968
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.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.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.keyguardOcclusionInteractor by
+ Kosmos.Fixture {
+ KeyguardOcclusionInteractor(
+ scope = testScope.backgroundScope,
+ repository = keyguardOcclusionRepository,
+ powerInteractor = powerInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
new file mode 100644
index 000000000000..9e34fe8d7c61
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.statusBarKeyguardViewManagerInteractor by
+ Kosmos.Fixture {
+ StatusBarKeyguardViewManagerInteractor(
+ keyguardTransitionInteractor = this.keyguardTransitionInteractor,
+ keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
+ powerInteractor = this.powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 000000000000..7ffa262d55a4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+ NotificationViewFlipperViewModel(
+ dumpManager = dumpManager,
+ stackInteractor = notificationStackInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 106e85cc8d85..c01366489c69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -23,7 +23,9 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -57,7 +59,9 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
communalInteractor = communalInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 5ae033c9870d..d798b3b13341 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -30,6 +30,8 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
private boolean mPowerSaveMode = false;
+ private boolean mIsPluggedIn = false;
+ private boolean mIsExtremePowerSave = false;
private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
@@ -64,8 +66,35 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
}
@Override
+ public boolean isExtremeSaverOn() {
+ return mIsExtremePowerSave;
+ }
+
+ /**
+ * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+ */
+ public void setExtremeSaverOn(Boolean extremePowerSave) {
+ if (extremePowerSave == mIsExtremePowerSave) return;
+
+ mIsExtremePowerSave = extremePowerSave;
+ for (BatteryStateChangeCallback callback: mCallbacks) {
+ callback.onExtremeBatterySaverChanged(extremePowerSave);
+ }
+ }
+
+ @Override
public boolean isPluggedIn() {
- return false;
+ return mIsPluggedIn;
+ }
+
+ /**
+ * Notifies all registered callbacks
+ */
+ public void setPluggedIn(boolean pluggedIn) {
+ mIsPluggedIn = pluggedIn;
+ for (BatteryStateChangeCallback cb : mCallbacks) {
+ cb.onBatteryLevelChanged(0, pluggedIn, false);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index a3b1a0eb260d..3938f77b9c54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -24,7 +24,7 @@ import android.testing.TestableLooper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.mediaOutputDialogFactory
+import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -42,7 +42,7 @@ val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44f23f5..23e269a67283 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -49,16 +51,22 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -102,6 +110,9 @@ public class WallpaperBackupAgent extends BackupAgent {
@VisibleForTesting
static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
+ @VisibleForTesting
+ static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
@@ -110,6 +121,11 @@ public class WallpaperBackupAgent extends BackupAgent {
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
+ /**
+ * An approximate area threshold to compare device dimension similarity
+ */
+ static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@ public class WallpaperBackupAgent extends BackupAgent {
private boolean mSystemHasLiveComponent;
private boolean mLockHasLiveComponent;
+ private DisplayManager mDisplayManager;
+
@Override
public void onCreate() {
if (DEBUG) {
@@ -137,6 +155,8 @@ public class WallpaperBackupAgent extends BackupAgent {
mBackupManager = new BackupManager(getBaseContext());
mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+ mDisplayManager = getSystemService(DisplayManager.class);
}
@Override
@@ -175,9 +195,11 @@ public class WallpaperBackupAgent extends BackupAgent {
mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
+ // performing backup of each file based on order of importance
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+ backupDeviceInfoFile(data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@ public class WallpaperBackupAgent extends BackupAgent {
}
}
+ /**
+ * This method backs up the device dimension information. The device data will always get
+ * overwritten when triggering a backup
+ */
+ private void backupDeviceInfoFile(FullBackupDataOutput data)
+ throws IOException {
+ final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+ // save the dimensions of the device with xml formatting
+ Point dimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
+
+ deviceInfoStage.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "dimensions");
+
+ out.startTag(null, "width");
+ out.text(String.valueOf(dimensions.x));
+ out.endTag(null, "width");
+
+ out.startTag(null, "height");
+ out.text(String.valueOf(dimensions.y));
+ out.endTag(null, "height");
+
+ if (smallerDisplay != null) {
+ out.startTag(null, "secondarywidth");
+ out.text(String.valueOf(secondaryDimensions.x));
+ out.endTag(null, "secondarywidth");
+
+ out.startTag(null, "secondaryheight");
+ out.text(String.valueOf(secondaryDimensions.y));
+ out.endTag(null, "secondaryheight");
+ }
+
+ out.endTag(null, "dimensions");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+
+ if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+ backupFile(deviceInfoStage, data);
+ }
+
private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
throws IOException {
final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@ public class WallpaperBackupAgent extends BackupAgent {
final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+ final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
boolean lockImageStageExists = lockImageStage.exists();
try {
+ // Parse the device dimensions of the source device and compare with target to
+ // to identify whether we need to skip the remainder of the restore process
+ Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+ deviceDimensionsStage);
+
+ Point targetDeviceDimensions = getScreenDimensions();
+ if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+ && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+ targetDeviceDimensions)) {
+ Slog.d(TAG, "The source device is significantly smaller than target");
+ }
+
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@ public class WallpaperBackupAgent extends BackupAgent {
infoStage.delete();
imageStage.delete();
lockImageStage.delete();
+ deviceDimensionsStage.delete();
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit()
@@ -409,6 +493,66 @@ public class WallpaperBackupAgent extends BackupAgent {
}
}
+ /**
+ * This method parses the given file for the backed up device dimensions
+ *
+ * @param deviceDimensions the file which holds the device dimensions
+ * @return the backed up device dimensions
+ */
+ private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+ int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(
+ new FileInputStream(deviceDimensions));
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ switch (name) {
+ case "width":
+ String widthText = readText(parser);
+ width = Integer.valueOf(widthText);
+ break;
+
+ case "height":
+ String textHeight = readText(parser);
+ height = Integer.valueOf(textHeight);
+ break;
+
+ case "secondarywidth":
+ String secondaryWidthText = readText(parser);
+ secondaryWidth = Integer.valueOf(secondaryWidthText);
+ break;
+
+ case "secondaryheight":
+ String secondaryHeightText = readText(parser);
+ secondaryHeight = Integer.valueOf(secondaryHeightText);
+ break;
+ default:
+ break;
+ }
+ }
+ return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static String readText(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
@VisibleForTesting
void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
@@ -691,6 +835,94 @@ public class WallpaperBackupAgent extends BackupAgent {
};
}
+ /**
+ * This method retrieves the dimensions of the largest display of the device
+ *
+ * @return a @{Point} object that contains the dimensions of the largest display on the device
+ */
+ private Point getScreenDimensions() {
+ Point largetDimensions = null;
+ int maxArea = 0;
+
+ for (Display display : getInternalDisplays()) {
+ Point displaySize = getRealSize(display);
+
+ int width = displaySize.x;
+ int height = displaySize.y;
+ int area = width * height;
+
+ if (area > maxArea) {
+ maxArea = area;
+ largetDimensions = displaySize;
+ }
+ }
+
+ return largetDimensions;
+ }
+
+ private Point getRealSize(Display display) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ display.getDisplayInfo(displayInfo);
+ return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+ }
+
+ /**
+ * This method returns the smaller display on a multi-display device
+ *
+ * @return Display that corresponds to the smaller display on a device or null if ther is only
+ * one Display on a device
+ */
+ private Display getSmallerDisplayIfExists() {
+ List<Display> internalDisplays = getInternalDisplays();
+ Point largestDisplaySize = getScreenDimensions();
+
+ // Find the first non-matching internal display
+ for (Display display : internalDisplays) {
+ Point displaySize = getRealSize(display);
+ if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+ return display;
+ }
+ }
+
+ // If no smaller display found, return null, as there is only a single display
+ return null;
+ }
+
+ /**
+ * This method retrieves the collection of Display objects available in the device.
+ * i.e. non-external displays are ignored
+ *
+ * @return list of displays corresponding to each display in the device
+ */
+ private List<Display> getInternalDisplays() {
+ Display[] allDisplays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+ List<Display> internalDisplays = new ArrayList<>();
+ for (Display display : allDisplays) {
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ internalDisplays.add(display);
+ }
+ }
+ return internalDisplays;
+ }
+
+ /**
+ * This method compares the source and target dimensions, and returns true if there is a
+ * significant difference in area between them and the source dimensions are smaller than the
+ * target dimensions.
+ *
+ * @param sourceDimensions is the dimensions of the source device
+ * @param targetDimensions is the dimensions of the target device
+ */
+ @VisibleForTesting
+ boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+ Point targetDimensions) {
+ int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+ - (sourceDimensions.x * sourceDimensions.y);
+ return rawAreaDelta > AREA_THRESHOLD;
+ }
+
@VisibleForTesting
boolean isDeviceInRestore() {
try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f101a5..ec9223c7d667 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@ public class WallpaperBackupAgentTest {
testParseCropHints(testMap);
}
+ @Test
+ public void test_sourceDimensionsAreLargerThanTarget() {
+ // source device is larger than target, expecting to get false
+ Point sourceDimensions = new Point(2208, 1840);
+ Point targetDimensions = new Point(1080, 2092);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(false);
+ }
+
+ @Test
+ public void test_sourceDimensionsMuchSmallerThanTarget() {
+ // source device is smaller than target, expecting to get true
+ Point sourceDimensions = new Point(1080, 2092);
+ Point targetDimensions = new Point(2208, 1840);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(true);
+ }
+
private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
assumeTrue(multiCrop());
mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 29b9d441cf38..fbd6709fd461 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -84,6 +84,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -98,6 +99,7 @@ import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AttributeSet;
@@ -148,6 +150,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -159,6 +162,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -187,6 +191,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// used to verify which request has successfully been received by the host.
private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
+ // Default reset interval for generated preview API rate limiting.
+ private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
+ Duration.ofHours(1).toMillis();
+ // Default max API calls per reset interval for generated preview API rate limiting.
+ private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
+
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -266,6 +277,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Mark widget lifecycle broadcasts as 'interactive'
private Bundle mInteractiveBroadcast;
+ private ApiCounter mGeneratedPreviewsApiCounter;
+
AppWidgetServiceImpl(Context context) {
mContext = context;
}
@@ -294,6 +307,17 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
+ final long generatedPreviewResetInterval = DeviceConfig.getLong(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
+ final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
+ mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
+ generatedPreviewMaxCallsPerInterval);
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+ new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
+
BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setBackgroundActivityStartsAllowed(false);
opts.setInteractive(true);
@@ -2480,6 +2504,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private void deleteProviderLocked(Provider provider) {
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
+ mGeneratedPreviewsApiCounter.remove(provider.id);
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -4004,7 +4029,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
@Override
- public void setWidgetPreview(@NonNull ComponentName providerComponent,
+ public boolean setWidgetPreview(@NonNull ComponentName providerComponent,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
final int userId = UserHandle.getCallingUserId();
@@ -4026,8 +4051,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ return true;
+ }
+ return false;
}
}
@@ -4068,6 +4097,26 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ private void handleSystemUiDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changed = properties.getKeyset();
+ synchronized (mLock) {
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS)) {
+ long resetIntervalMs = properties.getLong(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getResetIntervalMs());
+ mGeneratedPreviewsApiCounter.setResetIntervalMs(resetIntervalMs);
+ }
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL)) {
+ int maxCallsPerInterval = properties.getInt(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
+ mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
+ }
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4541,11 +4590,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
- private static final class ProviderId {
+ static final class ProviderId {
final int uid;
final ComponentName componentName;
- private ProviderId(int uid, ComponentName componentName) {
+ ProviderId(int uid, ComponentName componentName) {
this.uid = uid;
this.componentName = componentName;
}
@@ -4788,6 +4837,96 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ /**
+ * This class keeps track of API calls and implements rate limiting. One instance of this class
+ * tracks calls from all providers for one API, or a group of APIs that should share the same
+ * rate limit.
+ */
+ static final class ApiCounter {
+
+ private static final class ApiCallRecord {
+ // Number of times the API has been called for this provider.
+ public int apiCallCount = 0;
+ // The last time (from SystemClock.elapsedRealtime) the api call count was reset.
+ public long lastResetTimeMs = 0;
+
+ void reset(long nowMs) {
+ apiCallCount = 0;
+ lastResetTimeMs = nowMs;
+ }
+ }
+
+ private final Map<ProviderId, ApiCallRecord> mCallCount = new ArrayMap<>();
+ // The interval at which the call count is reset.
+ private long mResetIntervalMs;
+ // The max number of API calls per interval.
+ private int mMaxCallsPerInterval;
+ // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
+ private LongSupplier mMonotonicClock;
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
+ this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
+ }
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
+ LongSupplier monotonicClock) {
+ mResetIntervalMs = resetIntervalMs;
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ mMonotonicClock = monotonicClock;
+ }
+
+ public void setResetIntervalMs(long resetIntervalMs) {
+ mResetIntervalMs = resetIntervalMs;
+ }
+
+ public long getResetIntervalMs() {
+ return mResetIntervalMs;
+ }
+
+ public void setMaxCallsPerInterval(int maxCallsPerInterval) {
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ }
+
+ public int getMaxCallsPerInterval() {
+ return mMaxCallsPerInterval;
+ }
+
+ /**
+ * Returns true if the API call for the provider should be allowed, false if it should be
+ * rate-limited.
+ */
+ public boolean tryApiCall(@NonNull ProviderId provider) {
+ final ApiCallRecord record = getOrCreateRecord(provider);
+ final long now = mMonotonicClock.getAsLong();
+ final long timeSinceLastResetMs = now - record.lastResetTimeMs;
+ // If the last reset was beyond the reset interval, reset now.
+ if (timeSinceLastResetMs > mResetIntervalMs) {
+ record.reset(now);
+ }
+ if (record.apiCallCount < mMaxCallsPerInterval) {
+ record.apiCallCount++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the provider's call record from this counter, when the provider is no longer
+ * tracked.
+ */
+ public void remove(@NonNull ProviderId id) {
+ mCallCount.remove(id);
+ }
+
+ @NonNull
+ private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
+ if (!mCallCount.containsKey(provider)) {
+ mCallCount.put(provider, new ApiCallRecord());
+ }
+ return mCallCount.get(provider);
+ }
+ }
+
private class LoadedWidgetState {
final Widget widget;
final int hostTag;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 551297b253d1..7afb78033fb5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3935,6 +3935,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
@GuardedBy("mLock")
@Nullable
+ private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
+ final int numContexts = mContexts.size();
+ for (int i = numContexts - 1; i >= 0; i--) {
+ final FillContext context = mContexts.get(i);
+ final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
+ autofillId);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the latest non-empty value for the given id in the autofill contexts.
+ */
+ @GuardedBy("mLock")
+ @Nullable
private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
@@ -6417,7 +6435,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mClient.onGetCredentialException(id, viewId, exception.getType(),
exception.getMessage());
} else if (response != null) {
- mClient.onGetCredentialResponse(id, viewId, response);
+ if (viewId.isVirtualInt()) {
+ ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+ if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+ Bundle resultData = new Bundle();
+ resultData.putParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ response);
+ viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ resultData);
+ } else {
+ Slog.w(TAG, "View node not found after GetCredentialResponse");
+ }
+ } else {
+ mClient.onGetCredentialResponse(id, viewId, response);
+ }
} else {
Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
+ "and exception");
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 6d731b21ac8a..b1672ed9c732 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -210,7 +210,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -220,7 +220,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -229,7 +229,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
try {
mActivityListener.onDisplayEmpty(displayId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
};
@@ -1213,7 +1213,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
- mContext.getUser());
+ UserHandle.SYSTEM);
}
private void onSecureWindowShown(int displayId, int uid) {
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aabfefb..748253fc9194 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -106,7 +106,7 @@ public final class DropBoxManagerService extends SystemService {
private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
private static final int DEFAULT_MAX_FILES = 1000;
private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
- private static final int DEFAULT_QUOTA_KB = 10 * 1024;
+ private static final int DEFAULT_QUOTA_KB = Build.IS_USERDEBUG ? 20 * 1024 : 10 * 1024;
private static final int DEFAULT_QUOTA_PERCENT = 10;
private static final int DEFAULT_RESERVE_PERCENT = 0;
private static final int QUOTA_RESCAN_MILLIS = 5000;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 8dc15ade532e..25337a434329 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -100,7 +100,7 @@
"file_patterns": ["VcnManagementService\\.java"]
},
{
- "name": "FrameworksNetTests",
+ "name": "FrameworksVpnTests",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -163,9 +163,6 @@
}
],
"file_patterns": ["PinnerService\\.java"]
- },
- {
- "name": "FrameworksVpnTests"
}
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 663ba8a38d77..5e367097b78c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,7 +141,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -726,29 +725,19 @@ public class ActivityManagerService extends IActivityManager.Stub
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
- // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
- // For simplicity, since we statically declare the size of the array of BroadcastQueues,
- // we still create this new offload queue, but never ever put anything on it.
- final boolean mEnableOffloadQueue;
-
/**
* Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
* of the default {@link BroadcastQueueImpl}.
*/
final boolean mEnableModernQueue;
- static final int BROADCAST_QUEUE_FG = 0;
- static final int BROADCAST_QUEUE_BG = 1;
- static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
- static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
-
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
// Convenient for easy iteration over the queues. Foreground is first
// so that dispatch of foreground broadcasts gets precedence.
- final BroadcastQueue[] mBroadcastQueues;
+ private BroadcastQueue mBroadcastQueue;
@GuardedBy("this")
BroadcastStats mLastBroadcastStats;
@@ -758,43 +747,6 @@ public class ActivityManagerService extends IActivityManager.Stub
TraceErrorLogger mTraceErrorLogger;
- BroadcastQueue broadcastQueueForIntent(Intent intent) {
- return broadcastQueueForFlags(intent.getFlags(), intent);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags) {
- return broadcastQueueForFlags(flags, null);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
- if (mEnableModernQueue) {
- return mBroadcastQueues[0];
- }
-
- if (isOnFgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on foreground offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
- }
-
- if (isOnBgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on background offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
- }
-
- final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
- if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on "
- + (isFg ? "foreground" : "background") + " queue");
- return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
- : mBroadcastQueues[BROADCAST_QUEUE_BG];
- }
-
private volatile int mDeviceOwnerUid = INVALID_UID;
/**
@@ -2556,9 +2508,8 @@ public class ActivityManagerService extends IActivityManager.Stub
mInternal = new LocalService();
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
- mEnableOffloadQueue = false;
mEnableModernQueue = false;
- mBroadcastQueues = injector.getBroadcastQueues(this);
+ mBroadcastQueue = injector.getBroadcastQueue(this);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2599,12 +2550,10 @@ public class ActivityManagerService extends IActivityManager.Stub
? new OomAdjusterModernImpl(this, mProcessList, activeUids)
: new OomAdjuster(this, mProcessList, activeUids);
- mEnableOffloadQueue = SystemProperties.getBoolean(
- "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
mEnableModernQueue = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
- mBroadcastQueues = mInjector.getBroadcastQueues(this);
+ mBroadcastQueue = mInjector.getBroadcastQueue(this);
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
@@ -2671,6 +2620,14 @@ public class ActivityManagerService extends IActivityManager.Stub
mComponentAliasResolver = new ComponentAliasResolver(this);
}
+ void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+ mBroadcastQueue = broadcastQueue;
+ }
+
+ BroadcastQueue getBroadcastQueue() {
+ return mBroadcastQueue;
+ }
+
public void setSystemServiceManager(SystemServiceManager mgr) {
mSystemServiceManager = mgr;
}
@@ -4280,19 +4237,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// Clean-up disabled broadcast receivers.
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- packageName, disabledClasses, userId);
- }
+ mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ packageName, disabledClasses, userId);
}
final boolean clearBroadcastQueueForUserLocked(int userId) {
- boolean didSomething = false;
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- null, null, userId);
- }
+ boolean didSomething = mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ null, null, userId);
return didSomething;
}
@@ -4445,10 +4397,8 @@ public class ActivityManagerService extends IActivityManager.Stub
mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);
if (doit) {
- for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+ didSomething |= mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
packageName, null, userId);
- }
}
if (packageName == null || uninstalling || packageStateStopped) {
@@ -4515,9 +4465,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
// Take care of any broadcasts waiting for the process.
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationTimeoutLocked(app);
- }
+ mBroadcastQueue.onApplicationTimeoutLocked(app);
if (!isKillTimeout) {
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
app.killLocked("start timeout",
@@ -4779,36 +4727,47 @@ public class ActivityManagerService extends IActivityManager.Stub
// being bound to an application.
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
- } else if (instr2 != null) {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- instr2.mIsSdkInSandbox,
- providerList,
- instr2.mClass,
- profilerInfo, instr2.mArguments,
- instr2.mWatcher,
- instr2.mUiAutomationConnection, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
- new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
- mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
} else {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- /* isSdkInSandbox= */ false,
- providerList, null, profilerInfo, null, null, null, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
+ boolean isSdkInSandbox = false;
+ ComponentName instrumentationName = null;
+ Bundle instrumentationArgs = null;
+ IInstrumentationWatcher instrumentationWatcher = null;
+ IUiAutomationConnection instrumentationUiConnection = null;
+ if (instr2 != null) {
+ isSdkInSandbox = instr2.mIsSdkInSandbox;
+ instrumentationName = instr2.mClass;
+ instrumentationArgs = instr2.mArguments;
+ instrumentationWatcher = instr2.mWatcher;
+ instrumentationUiConnection = instr2.mUiAutomationConnection;
+ }
+ thread.bindApplication(
+ processName,
+ appInfo,
+ app.sdkSandboxClientAppVolumeUuid,
+ app.sdkSandboxClientAppPackage,
+ isSdkInSandbox,
+ providerList,
+ instrumentationName,
+ profilerInfo,
+ instrumentationArgs,
+ instrumentationWatcher,
+ instrumentationUiConnection,
+ testMode,
+ mBinderTransactionTrackingEnabled,
+ enableTrackAllocation,
+ isRestrictedBackupMode || !normalMode,
+ app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
+ app.getCompat(),
+ getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
+ buildSerial,
+ autofillOptions,
+ contentCaptureOptions,
+ app.getDisabledCompatChanges(),
+ serializedSystemFontMap,
+ app.getStartElapsedTime(),
+ app.getStartUptime());
}
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
@@ -4948,9 +4907,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Check if a next-broadcast receiver is in this process...
if (!badApp) {
try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
+ didSomething |= mBroadcastQueue.onApplicationAttachedLocked(app);
checkTime(startTime, "finishAttachApplicationInner: "
+ "after dispatching broadcasts");
} catch (BroadcastDeliveryFailedException e) {
@@ -9090,9 +9047,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private void startBroadcastObservers() {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.start(mContext.getContentResolver());
- }
+ mBroadcastQueue.start(mContext.getContentResolver());
}
private void updateForceBackgroundCheck(boolean enabled) {
@@ -9363,7 +9318,9 @@ public class ActivityManagerService extends IActivityManager.Stub
sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
}
if (info.broadcastIntentAction != null) {
- sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+ sb.append("Broadcast-Intent-Action: ")
+ .append(info.broadcastIntentAction)
+ .append("\n");
}
if (info.durationMillis != -1) {
sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
@@ -9723,82 +9680,126 @@ public class ActivityManagerService extends IActivityManager.Stub
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
final boolean runSynchronously = process == null;
- Thread worker = new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- if (report != null) {
- sb.append(report);
- }
-
- String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
- String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
- int lines = Build.IS_USER
- ? 0
- : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
- int dropboxMaxSize = Settings.Global.getInt(
- mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
-
- if (dataFile != null) {
- // Attach the stack traces file to the report so collectors can load them
- // by file if they have access.
- sb.append(DATA_FILE_PATH_HEADER)
- .append(dataFile.getAbsolutePath()).append('\n');
-
- int maxDataFileSize = dropboxMaxSize
- - sb.length()
- - lines * RESERVED_BYTES_PER_LOGCAT_LINE
- - DATA_FILE_PATH_FOOTER.length();
-
- if (maxDataFileSize > 0) {
- // Inline dataFile contents if there is room.
- try {
- sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
- "\n\n[[TRUNCATED]]\n"));
- } catch (IOException e) {
- Slog.e(TAG, "Error reading " + dataFile, e);
+ Thread worker =
+ new Thread("Error dump: " + dropboxTag) {
+ @Override
+ public void run() {
+ if (report != null) {
+ sb.append(report);
}
- }
- // Always append the footer, even there wasn't enough space to inline the
- // dataFile contents.
- sb.append(DATA_FILE_PATH_FOOTER);
- }
+ String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+ String maxBytesSetting =
+ Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+ int lines =
+ Build.IS_USER
+ ? 0
+ : Settings.Global.getInt(
+ mContext.getContentResolver(), logcatSetting, 0);
+ int dropboxMaxSize =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ maxBytesSetting,
+ DROPBOX_DEFAULT_MAX_SIZE);
+
+ if (dataFile != null) {
+ // Attach the stack traces file to the report so collectors can load
+ // them
+ // by file if they have access.
+ sb.append(DATA_FILE_PATH_HEADER)
+ .append(dataFile.getAbsolutePath())
+ .append('\n');
+
+ int maxDataFileSize =
+ dropboxMaxSize
+ - sb.length()
+ - lines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - DATA_FILE_PATH_FOOTER.length();
+
+ if (maxDataFileSize > 0) {
+ // Inline dataFile contents if there is room.
+ try {
+ sb.append(
+ FileUtils.readTextFile(
+ dataFile,
+ maxDataFileSize,
+ "\n\n[[TRUNCATED]]\n"));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading " + dataFile, e);
+ }
+ }
- if (crashInfo != null && crashInfo.stackTrace != null) {
- sb.append(crashInfo.stackTrace);
- }
+ // Always append the footer, even there wasn't enough space to inline
+ // the
+ // dataFile contents.
+ sb.append(DATA_FILE_PATH_FOOTER);
+ }
+
+ if (crashInfo != null && crashInfo.stackTrace != null) {
+ sb.append(crashInfo.stackTrace);
+ }
- if (lines > 0 && !runSynchronously) {
- sb.append("\n");
+ if (lines > 0 && !runSynchronously) {
+ sb.append("\n");
- InputStreamReader input = null;
- try {
- java.lang.Process logcat = new ProcessBuilder(
- // Time out after 10s of inactivity, but kill logcat with SEGV
- // so we can investigate why it didn't finish.
- "/system/bin/timeout", "-i", "-s", "SEGV", "10s",
- // Merge several logcat streams, and take the last N lines.
- "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
- "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
- .redirectErrorStream(true).start();
-
- try { logcat.getOutputStream().close(); } catch (IOException e) {}
- try { logcat.getErrorStream().close(); } catch (IOException e) {}
- input = new InputStreamReader(logcat.getInputStream());
-
- int num;
- char[] buf = new char[8192];
- while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
- } catch (IOException e) {
- Slog.e(TAG, "Error running logcat", e);
- } finally {
- if (input != null) try { input.close(); } catch (IOException e) {}
- }
- }
+ InputStreamReader input = null;
+ try {
+ java.lang.Process logcat =
+ new ProcessBuilder(
+ // Time out after 10s of inactivity, but
+ // kill logcat with SEGV
+ // so we can investigate why it didn't
+ // finish.
+ "/system/bin/timeout",
+ "-i",
+ "-s",
+ "SEGV",
+ "10s",
+ // Merge several logcat streams, and take
+ // the last N lines.
+ "/system/bin/logcat",
+ "-v",
+ "threadtime",
+ "-b",
+ "events",
+ "-b",
+ "system",
+ "-b",
+ "main",
+ "-b",
+ "crash",
+ "-t",
+ String.valueOf(lines))
+ .redirectErrorStream(true)
+ .start();
- dbox.addText(dropboxTag, sb.toString());
- }
- };
+ try {
+ logcat.getOutputStream().close();
+ } catch (IOException e) {
+ }
+ try {
+ logcat.getErrorStream().close();
+ } catch (IOException e) {
+ }
+ input = new InputStreamReader(logcat.getInputStream());
+
+ int num;
+ char[] buf = new char[8192];
+ while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running logcat", e);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ dbox.addText(dropboxTag, sb.toString());
+ }
+ };
if (runSynchronously) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10166,43 +10167,48 @@ public class ActivityManagerService extends IActivityManager.Stub
mOomAdjuster.dumpCacheOomRankerSettings(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
if (dumpAll || dumpPackage != null) {
dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
}
mCpHelper.dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage);
if (!dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpLocked();
}
@@ -10217,7 +10223,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// method with the lock held.
if (dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpWithClient();
}
@@ -10230,33 +10237,38 @@ public class ActivityManagerService extends IActivityManager.Stub
// proxies in the first place.
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBinderProxies(pw, BINDER_PROXY_HIGH_WATERMARK /* minToDump */);
}
synchronized(this) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10266,7 +10278,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!dumpNormalPriority) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10274,45 +10287,53 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mAssociations.size() > 0) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mOomAdjProfiler.dump(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpLmkLocked(pw);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
synchronized (mProcLock) {
mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpUsers(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mComponentAliasResolver.dump(pw);
}
@@ -10322,7 +10343,8 @@ public class ActivityManagerService extends IActivityManager.Stub
* Dump the app restriction controller, it's required not to hold the global lock here.
*/
private void dumpAppRestrictionController(PrintWriter pw) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mAppRestrictionController.dump(pw, "");
}
@@ -11431,9 +11453,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
- for (BroadcastQueue q : mBroadcastQueues) {
- q.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- }
+ mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
synchronized (mStickyBroadcasts) {
for (int user = 0; user < mStickyBroadcasts.size(); user++) {
long token = proto.start(
@@ -11462,7 +11482,9 @@ public class ActivityManagerService extends IActivityManager.Stub
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
- pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+ pw.println(
+ "ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity"
+ + " allowed-associations)");
boolean printed = false;
if (mAllowedAssociations != null) {
for (int i = 0; i < mAllowedAssociations.size(); i++) {
@@ -11581,11 +11603,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (!onlyReceivers) {
- for (BroadcastQueue q : mBroadcastQueues) {
- needSep = q.dumpLocked(fd, pw, args, opti,
- dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
- printedAnything |= needSep;
- }
+ needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+ printedAnything |= needSep;
}
needSep = true;
@@ -11641,9 +11661,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!onlyHistory && !onlyReceivers && dumpAll) {
pw.println();
- for (BroadcastQueue queue : mBroadcastQueues) {
- pw.println(" Queue " + queue.toString() + ": " + queue.describeStateLocked());
- }
+ pw.println(" Queue " + mBroadcastQueue.toString() + ": "
+ + mBroadcastQueue.describeStateLocked());
pw.println(" mHandler:");
mHandler.dump(new PrintWriterPrinter(pw), " ");
needSep = true;
@@ -12816,7 +12835,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss
+ kernelUsed)); pw.print(" (");
- pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + ");
+ pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss));
+ pw.print(" used pss + ");
pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n");
pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM));
} else {
@@ -13490,9 +13510,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
}
mAppProfiler.onCleanupApplicationRecordLocked(app);
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationCleanupLocked(app);
- }
+ mBroadcastQueue.onApplicationCleanupLocked(app);
clearProcessForegroundLocked(app);
mServices.killServicesLocked(app, allowRestart);
mPhantomProcessList.onAppDied(pid);
@@ -13686,8 +13704,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
validateServiceInstanceName(instanceName);
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
- "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
+ if (DEBUG_SERVICE)
+ Slog.v(
+ TAG_SERVICE,
+ "*** startService: "
+ + service
+ + " type="
+ + resolvedType
+ + " fg="
+ + requireForeground);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -14599,7 +14624,7 @@ public class ActivityManagerService extends IActivityManager.Stub
originalStickyCallingUid))) {
sticky = broadcast.intent;
}
- BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
+ BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE,
BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
@@ -15448,9 +15473,14 @@ public class ActivityManagerService extends IActivityManager.Stub
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
callingPid, callingUid)
!= PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
- + callingPid + ", uid=" + callingUid
- + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+ String msg =
+ "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+ + " pid="
+ + callingPid
+ + ", uid="
+ + callingUid
+ + " requires "
+ + android.Manifest.permission.BROADCAST_STICKY;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -15593,7 +15623,7 @@ public class ActivityManagerService extends IActivityManager.Stub
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
- final BroadcastQueue queue = broadcastQueueForIntent(intent);
+ final BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
@@ -15686,7 +15716,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if ((receivers != null && receivers.size() > 0)
|| resultTo != null) {
- BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastQueue queue = mBroadcastQueue;
filterNonExportedComponents(intent, callingUid, callingPid, receivers,
mPlatformCompat, callerPackage, resolvedType);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
@@ -15974,9 +16004,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
void backgroundServicesFinishedLocked(int userId) {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.backgroundServicesFinishedLocked(userId);
- }
+ mBroadcastQueue.backgroundServicesFinishedLocked(userId);
}
public void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -15997,8 +16025,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
- final BroadcastQueue queue = broadcastQueueForFlags(flags);
- queue.finishReceiverLocked(callerApp, resultCode,
+ mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -16589,10 +16616,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// =========================================================
boolean isReceivingBroadcastLocked(ProcessRecord app, int[] outSchedGroup) {
- int res = ProcessList.SCHED_GROUP_UNDEFINED;
- for (BroadcastQueue queue : mBroadcastQueues) {
- res = Math.max(res, queue.getPreferredSchedulingGroupLocked(app));
- }
+ final int res = mBroadcastQueue.getPreferredSchedulingGroupLocked(app);
outSchedGroup[0] = res;
return res != ProcessList.SCHED_GROUP_UNDEFINED;
}
@@ -16716,10 +16740,8 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
final boolean canGcNowLocked() {
- for (BroadcastQueue q : mBroadcastQueues) {
- if (!q.isIdleLocked()) {
- return false;
- }
+ if (!mBroadcastQueue.isIdleLocked()) {
+ return false;
}
return mAtmInternal.canGcNow();
}
@@ -17929,9 +17951,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
void onProcessFreezableChangedLocked(ProcessRecord app) {
- if (mEnableModernQueue) {
- mBroadcastQueues[0].onProcessFreezableChangedLocked(app);
- }
+ mBroadcastQueue.onProcessFreezableChangedLocked(app);
}
@VisibleForTesting
@@ -19622,9 +19642,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForIdle(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForIdle(pw);
- }
+ mBroadcastQueue.waitForIdle(pw);
pw.println("All broadcast queues are idle!");
pw.flush();
}
@@ -19640,9 +19658,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForBarrier(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForBarrier(pw);
- }
+ mBroadcastQueue.waitForBarrier(pw);
if (flushApplicationThreads) {
waitForApplicationBarrier(pw);
}
@@ -19718,9 +19734,7 @@ public class ActivityManagerService extends IActivityManager.Stub
void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForDispatched(intent, pw);
- }
+ mBroadcastQueue.waitForDispatched(intent, pw);
}
void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
@@ -19765,9 +19779,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
- }
+ mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
}
@Override
@@ -20321,7 +20333,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return mNmi != null;
}
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
// Broadcast policy parameters
final BroadcastConstants foreConstants = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS);
@@ -20337,26 +20349,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// by default, no "slow" policy in this queue
offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
- final BroadcastQueue[] broadcastQueues;
- final Handler handler = service.mHandler;
- if (service.mEnableModernQueue) {
- broadcastQueues = new BroadcastQueue[1];
- broadcastQueues[0] = new BroadcastQueueModernImpl(service, handler,
+ return new BroadcastQueueModernImpl(service, service.mHandler,
foreConstants, backConstants);
- } else {
- broadcastQueues = new BroadcastQueue[4];
- broadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(service, handler,
- "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
- broadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(service, handler,
- "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_bg", offloadConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_fg", foreConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- }
- return broadcastQueues;
}
/** @see Binder#getCallingUid */
@@ -20678,14 +20672,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private boolean isOnFgOffloadQueue(int flags) {
- return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
- }
-
- private boolean isOnBgOffloadQueue(int flags) {
- return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
- }
-
@Override
public ParcelFileDescriptor getLifeMonitor() {
if (!isCallerShell()) {
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
deleted file mode 100644
index 8aa3921d3f2f..000000000000
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.LocalServices;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-
-/**
- * Manages ordered broadcast delivery, applying policy to mitigate the effects of
- * slow receivers.
- */
-public class BroadcastDispatcher {
- private static final String TAG = "BroadcastDispatcher";
-
- // Deferred broadcasts to one app; times are all uptime time base like
- // other broadcast-related timekeeping
- static class Deferrals {
- final int uid;
- long deferredAt; // when we started deferring
- long deferredBy; // how long did we defer by last time?
- long deferUntil; // when does the next element become deliverable?
- int alarmCount;
-
- final ArrayList<BroadcastRecord> broadcasts;
-
- Deferrals(int uid, long now, long backoff, int count) {
- this.uid = uid;
- this.deferredAt = now;
- this.deferredBy = backoff;
- this.deferUntil = now + backoff;
- this.alarmCount = count;
- broadcasts = new ArrayList<>();
- }
-
- void add(BroadcastRecord br) {
- broadcasts.add(br);
- }
-
- int size() {
- return broadcasts.size();
- }
-
- boolean isEmpty() {
- return broadcasts.isEmpty();
- }
-
- @NeverCompile
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (BroadcastRecord br : broadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- void dumpLocked(Dumper d) {
- for (BroadcastRecord br : broadcasts) {
- d.dump(br);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Deferrals{uid=");
- sb.append(uid);
- sb.append(", deferUntil=");
- sb.append(deferUntil);
- sb.append(", #broadcasts=");
- sb.append(broadcasts.size());
- sb.append("}");
- return sb.toString();
- }
- }
-
- // Carrying dump formatting state across multiple concatenated datasets
- class Dumper {
- final PrintWriter mPw;
- final String mQueueName;
- final String mDumpPackage;
- final SimpleDateFormat mSdf;
- boolean mPrinted;
- boolean mNeedSep;
- String mHeading;
- String mLabel;
- int mOrdinal;
-
- Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
- mPw = pw;
- mQueueName = queueName;
- mDumpPackage = dumpPackage;
- mSdf = sdf;
-
- mPrinted = false;
- mNeedSep = true;
- }
-
- void setHeading(String heading) {
- mHeading = heading;
- mPrinted = false;
- }
-
- void setLabel(String label) {
- //" Active Ordered Broadcast " + mQueueName + " #" + i + ":"
- mLabel = " " + label + " " + mQueueName + " #";
- mOrdinal = 0;
- }
-
- boolean didPrint() {
- return mPrinted;
- }
-
- @NeverCompile
- void dump(BroadcastRecord br) {
- if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
- if (!mPrinted) {
- if (mNeedSep) {
- mPw.println();
- }
- mPrinted = true;
- mNeedSep = true;
- mPw.println(" " + mHeading + " [" + mQueueName + "]:");
- }
- mPw.println(mLabel + mOrdinal + ":");
- mOrdinal++;
-
- br.dump(mPw, " ", mSdf);
- }
- }
- }
-
- private final Object mLock;
- private final BroadcastQueueImpl mQueue;
- private final BroadcastConstants mConstants;
- private final Handler mHandler;
- private AlarmManagerInternal mAlarm;
-
- // Current alarm targets; mapping uid -> in-flight alarm count
- final SparseIntArray mAlarmUids = new SparseIntArray();
- final AlarmManagerInternal.InFlightListener mAlarmListener =
- new AlarmManagerInternal.InFlightListener() {
- @Override
- public void broadcastAlarmPending(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
- mAlarmUids.put(recipientUid, newCount);
- // any deferred broadcasts to this app now get fast-tracked
- final int numEntries = mDeferredBroadcasts.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mDeferredBroadcasts.get(i).uid) {
- Deferrals d = mDeferredBroadcasts.remove(i);
- mAlarmDeferrals.add(d);
- break;
- }
- }
- }
- }
-
- @Override
- public void broadcastAlarmComplete(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
- if (newCount >= 0) {
- mAlarmUids.put(recipientUid, newCount);
- } else {
- Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
- mAlarmUids.put(recipientUid, 0);
- }
-
- // No longer an alarm target, so resume ordinary deferral policy
- if (newCount <= 0) {
- final int numEntries = mAlarmDeferrals.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mAlarmDeferrals.get(i).uid) {
- Deferrals d = mAlarmDeferrals.remove(i);
- insertLocked(mDeferredBroadcasts, d);
- break;
- }
- }
- }
- }
- }
- };
-
- // Queue recheck operation used to tickle broadcast delivery when appropriate
- final Runnable mScheduleRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.v(TAG, "Deferral recheck of pending broadcasts");
- }
- mQueue.scheduleBroadcastsLocked();
- mRecheckScheduled = false;
- }
- }
- };
- private boolean mRecheckScheduled = false;
-
- // Usual issuance-order outbound queue
- private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
- // General deferrals not holding up alarms
- private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
- // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
- private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
- // Under the "deliver alarm broadcasts immediately" policy, the queue of
- // upcoming alarm broadcasts. These are always delivered first - if the
- // policy is changed on the fly from immediate-alarm-delivery to the previous
- // in-order-queueing behavior, pending immediate alarm deliveries will drain
- // and then the behavior settle into the pre-U semantics.
- private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
-
- // Next outbound broadcast, established by getNextBroadcastLocked()
- private BroadcastRecord mCurrentBroadcast;
-
- // Map userId to its deferred boot completed broadcasts.
- private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>();
-
- /**
- * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user.
- */
- static class DeferredBootCompletedBroadcastPerUser {
- private int mUserId;
- // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED
- // receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray();
- // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray();
- // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts.
- // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has
- // any process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>();
- // is the LOCKED_BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mLockedBootCompletedBroadcastReceived;
- // Map UID to deferred BOOT_COMPLETED broadcasts.
- // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any
- // process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>();
- // is the BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mBootCompletedBroadcastReceived;
-
- DeferredBootCompletedBroadcastPerUser(int userId) {
- this.mUserId = userId;
- }
-
- public void updateUidReady(int uid) {
- if (!mLockedBootCompletedBroadcastReceived
- || mDeferredLockedBootCompletedBroadcasts.size() != 0) {
- mUidReadyForLockedBootCompletedBroadcast.put(uid, true);
- }
- if (!mBootCompletedBroadcastReceived
- || mDeferredBootCompletedBroadcasts.size() != 0) {
- mUidReadyForBootCompletedBroadcast.put(uid, true);
- }
- }
-
- public void enqueueBootCompletedBroadcasts(String action,
- SparseArray<BroadcastRecord> deferred) {
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast);
- mLockedBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts);
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast);
- mBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts);
- }
- }
- }
-
- /**
- * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- * @param from the UID to BroadcastRecord map.
- * @param into The UID to list of BroadcastRecord map.
- */
- private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from,
- SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) {
- // remove unwanted uids from uidReadyForReceiver.
- for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) {
- if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) {
- uidReadyForReceiver.removeAt(i);
- }
- }
- for (int i = 0, size = from.size(); i < size; i++) {
- final int uid = from.keyAt(i);
- into.put(uid, from.valueAt(i));
- if (uidReadyForReceiver.indexOfKey(uid) < 0) {
- // uid is wanted but not ready.
- uidReadyForReceiver.put(uid, false);
- }
- }
- }
-
- public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- boolean isAllUidReady) {
- BroadcastRecord next = dequeueDeferredBootCompletedBroadcast(
- mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast, isAllUidReady);
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast, isAllUidReady);
- }
- return next;
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver,
- boolean isAllUidReady) {
- for (int i = 0, size = uid2br.size(); i < size; i++) {
- final int uid = uid2br.keyAt(i);
- if (isAllUidReady || uidReadyForReceiver.get(uid)) {
- final BroadcastRecord br = uid2br.valueAt(i);
- if (DEBUG_BROADCAST_DEFERRAL) {
- final Object receiver = br.receivers.get(0);
- if (receiver instanceof BroadcastFilter) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " BroadcastFilter:" + (BroadcastFilter) receiver
- + " broadcast:" + br.intent.getAction());
- }
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- String packageName = info.activityInfo.applicationInfo.packageName;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " packageName:" + packageName
- + " broadcast:" + br.intent.getAction());
- }
- }
- }
- // remove the BroadcastRecord.
- uid2br.removeAt(i);
- if (uid2br.size() == 0) {
- // All deferred receivers are executed, do not need uidReadyForReceiver
- // any more.
- uidReadyForReceiver.clear();
- }
- return br;
- }
- }
- return null;
- }
-
- private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) {
- SparseArray<BroadcastRecord> brs = null;
- if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
- brs = mDeferredLockedBootCompletedBroadcasts;
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- brs = mDeferredBootCompletedBroadcasts;
- }
- return brs;
- }
-
- /**
- * Return the total number of UIDs in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- return brs != null ? brs.size() : 0;
- }
-
- /**
- * Return the total number of receivers in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return 0;
- }
- int size = 0;
- for (int i = 0, s = brs.size(); i < s; i++) {
- size += brs.valueAt(i).receivers.size();
- }
- return size;
- }
-
- @NeverCompile
- public void dump(Dumper dumper, String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return;
- }
- for (int i = 0, size = brs.size(); i < size; i++) {
- dumper.dump(brs.valueAt(i));
- }
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
- for (int i = 0, size = brs.size(); i < size; i++) {
- final Object receiver = brs.valueAt(i).receivers.get(0);
- String packageName = null;
- if (receiver instanceof BroadcastFilter) {
- BroadcastFilter recv = (BroadcastFilter) receiver;
- packageName = recv.receiverList.app.processName;
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- packageName = info.activityInfo.applicationInfo.packageName;
- }
- Slog.i(TAG, "uid:" + brs.keyAt(i)
- + " packageName:" + packageName
- + " receivers:" + brs.valueAt(i).receivers.size());
- }
- }
- }
-
- private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) {
- if (mUser2Deferred.contains(userId)) {
- return mUser2Deferred.get(userId);
- } else {
- final DeferredBootCompletedBroadcastPerUser temp =
- new DeferredBootCompletedBroadcastPerUser(userId);
- mUser2Deferred.put(userId, temp);
- return temp;
- }
- }
-
- /**
- * ActivityManagerService.attachApplication() call this method to notify that the UID is ready
- * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid);
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() {
- final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast
- == DEFER_BOOT_COMPLETED_BROADCAST_NONE);
- BroadcastRecord next = null;
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady);
- if (next != null) {
- break;
- }
- }
- return next;
- }
-
- /**
- * Constructed & sharing a lock with its associated BroadcastQueue instance
- */
- public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
- Handler handler, Object lock) {
- mQueue = queue;
- mConstants = constants;
- mHandler = handler;
- mLock = lock;
- }
-
- /**
- * Spin up the integration with the alarm manager service; done lazily to manage
- * service availability ordering during boot.
- */
- public void start() {
- // Set up broadcast alarm tracking
- mAlarm = LocalServices.getService(AlarmManagerInternal.class);
- mAlarm.registerInFlightListener(mAlarmListener);
- }
-
- /**
- * Standard contents-are-empty check
- */
- public boolean isEmpty() {
- synchronized (mLock) {
- return isIdle()
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0;
- }
- }
-
- /**
- * Have less check than {@link #isEmpty()}.
- * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED
- * broadcasts because those can be deferred until the first time the uid's process is started.
- * @return
- */
- public boolean isIdle() {
- synchronized (mLock) {
- return mCurrentBroadcast == null
- && mOrderedBroadcasts.isEmpty()
- && mAlarmQueue.isEmpty()
- && isDeferralsListEmpty(mDeferredBroadcasts)
- && isDeferralsListEmpty(mAlarmDeferrals);
- }
- }
-
- private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (list.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
- return false;
- }
- return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
- && isBeyondBarrier(mAlarmQueue, barrierTime)
- && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
- && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
- }
- }
-
- private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (!isDispatched(list.get(i).broadcasts, intent)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isDispatched(@NonNull Intent intent) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
- return false;
- }
- return isDispatched(mOrderedBroadcasts, intent)
- && isDispatched(mAlarmQueue, intent)
- && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
- && isDispatchedInDeferrals(mAlarmDeferrals, intent);
- }
- }
-
- private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
- int pending = 0;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- pending += list.get(i).size();
- }
- return pending;
- }
-
- private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) {
- return pendingInDeferralsList(list) == 0;
- }
-
- /**
- * Strictly for logging, describe the currently pending contents in a human-
- * readable way
- */
- public String describeStateLocked() {
- final StringBuilder sb = new StringBuilder(128);
- if (mCurrentBroadcast != null) {
- sb.append("1 in flight, ");
- }
- sb.append(mOrderedBroadcasts.size());
- sb.append(" ordered");
- int n = mAlarmQueue.size();
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" alarms");
- }
- n = pendingInDeferralsList(mAlarmDeferrals);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferrals in alarm recipients");
- }
- n = pendingInDeferralsList(mDeferredBroadcasts);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred");
- }
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred LOCKED_BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED));
- sb.append(" receivers");
- }
-
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED));
- sb.append(" receivers");
- }
- return sb.toString();
- }
-
- // ----------------------------------
- // BroadcastQueue operation support
- void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- final ArrayList<BroadcastRecord> queue =
- (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
- ? mAlarmQueue
- : mOrderedBroadcasts;
-
- if (r.receivers == null || r.receivers.isEmpty()) {
- // Fast no-op path for broadcasts that won't actually be dispatched to
- // receivers - we still need to handle completion callbacks and historical
- // records, but we don't need to consider the fancy cases.
- queue.add(r);
- return;
- }
-
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else {
- // Ordinary broadcast, so put it on the appropriate queue and carry on
- queue.add(r);
- }
- }
-
- /**
- * Return the total number of UIDs in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action);
- }
- return size;
- }
-
- /**
- * Return the total number of receivers in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action);
- }
- return size;
- }
-
- // Returns the now-replaced broadcast record, or null if none
- BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
- // Simple case, in the ordinary queue.
- BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
- // ... or possibly in the simple alarm queue
- if (old == null) {
- old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
- }
- // If we didn't find it, less-simple: in a deferral queue?
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
- }
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
- }
- return old;
- }
-
- private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- final Deferrals d = list.get(i);
- old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
- if (old != null) {
- return old;
- }
- }
- return null;
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final Intent intent = r.intent;
- // Any in-flight broadcast has already been popped, and cannot be replaced.
- // (This preserves existing behavior of the replacement API)
- for (int i = list.size() - 1; i >= 0; i--) {
- old = list.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "***** Replacing " + typeForLogging
- + " [" + mQueue.mQueueName + "]: " + intent);
- }
- // Clone deferral state too if any
- r.deferred = old.deferred;
- list.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- boolean cleanupDisabledPackageReceiversLocked(final String packageName,
- Set<String> filterByClasses, final int userId, final boolean doit) {
- // Note: fast short circuits when 'doit' is false, as soon as we hit any
- // "yes we would do something" circumstance
- boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
- packageName, filterByClasses, userId, doit);
- if (doit || !didSomething) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- lockedBootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(
- lockedBootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- bootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if ((doit || !didSomething) && mCurrentBroadcast != null) {
- didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, doit);
- }
-
- return didSomething;
- }
-
- private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (Deferrals d : list) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
- packageName, filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (BroadcastRecord br : list) {
- didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- /**
- * Standard proto dump entry point
- */
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- if (mCurrentBroadcast != null) {
- mCurrentBroadcast.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mAlarmQueue) {
- br.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpDebug(proto, fieldId);
- }
-
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- // ----------------------------------
- // Dispatch & deferral management
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mCurrentBroadcast;
- }
-
- /**
- * If there is a deferred broadcast that is being sent to an alarm target, return
- * that one. If there's no deferred alarm target broadcast but there is one
- * that has reached the end of its deferral, return that.
- *
- * This stages the broadcast internally until it is retired, and returns that
- * staged record if this is called repeatedly, until retireBroadcast(r) is called.
- */
- public BroadcastRecord getNextBroadcastLocked(final long now) {
- if (mCurrentBroadcast != null) {
- return mCurrentBroadcast;
- }
-
- BroadcastRecord next = null;
-
- // Alarms in flight take precedence over everything else. This queue
- // will be non-empty only when the relevant policy is in force, but if
- // policy has changed on the fly we still need to drain this before we
- // settle into the legacy behavior.
- if (!mAlarmQueue.isEmpty()) {
- next = mAlarmQueue.remove(0);
- }
-
- // Next in precedence are deferred BOOT_COMPLETED broadcasts
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast();
- }
-
- // Alarm-related deferrals are next in precedence...
- if (next == null && !mAlarmDeferrals.isEmpty()) {
- next = popLocked(mAlarmDeferrals);
- if (DEBUG_BROADCAST_DEFERRAL && next != null) {
- Slog.i(TAG, "Next broadcast from alarm targets: " + next);
- }
- }
-
- final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
- if (next == null && !mDeferredBroadcasts.isEmpty()) {
- // A this point we're going to deliver either:
- // 1. the next "overdue" deferral; or
- // 2. the next ordinary ordered broadcast; *or*
- // 3. the next not-yet-overdue deferral.
-
- for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
- Deferrals d = mDeferredBroadcasts.get(i);
- if (now < d.deferUntil && someQueued) {
- // stop looking when we haven't hit the next time-out boundary
- // but only if we have un-deferred broadcasts waiting,
- // otherwise we can deliver whatever deferred broadcast
- // is next available.
- break;
- }
-
- if (d.broadcasts.size() > 0) {
- next = d.broadcasts.remove(0);
- // apply deferral-interval decay policy and move this uid's
- // deferred broadcasts down in the delivery queue accordingly
- mDeferredBroadcasts.remove(i); // already 'd'
- d.deferredBy = calculateDeferral(d.deferredBy);
- d.deferUntil += d.deferredBy;
- insertLocked(mDeferredBroadcasts, d);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from deferrals " + next
- + ", deferUntil now " + d.deferUntil);
- }
- break;
- }
- }
- }
-
- if (next == null && someQueued) {
- next = mOrderedBroadcasts.remove(0);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from main queue: " + next);
- }
- }
-
- mCurrentBroadcast = next;
- return next;
- }
-
- /**
- * Called after the broadcast queue finishes processing the currently
- * active broadcast (obtained by calling getNextBroadcastLocked()).
- */
- public void retireBroadcastLocked(final BroadcastRecord r) {
- // ERROR if 'r' is not the active broadcast
- if (r != mCurrentBroadcast) {
- Slog.wtf(TAG, "Retiring broadcast " + r
- + " doesn't match current outgoing " + mCurrentBroadcast);
- }
- mCurrentBroadcast = null;
- }
-
- /**
- * Called prior to broadcast dispatch to check whether the intended
- * recipient is currently subject to deferral policy.
- */
- public boolean isDeferringLocked(final int uid) {
- Deferrals d = findUidLocked(uid);
- if (d != null && d.broadcasts.isEmpty()) {
- // once we've caught up with deferred broadcasts to this uid
- // and time has advanced sufficiently that we wouldn't be
- // deferring newly-enqueued ones, we're back to normal policy.
- if (SystemClock.uptimeMillis() >= d.deferUntil) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
- }
- removeDeferral(d);
- return false;
- }
- }
- return (d != null);
- }
-
- /**
- * Defer broadcasts for the given app. If 'br' is non-null, this also makes
- * sure that broadcast record is enqueued as the next upcoming broadcast for
- * the app.
- */
- public void startDeferring(final int uid) {
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
-
- // If we're not yet tracking this app, set up that bookkeeping
- if (d == null) {
- // Start a new deferral
- final long now = SystemClock.uptimeMillis();
- d = new Deferrals(uid,
- now,
- mConstants.DEFERRAL,
- mAlarmUids.get(uid, 0));
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Now deferring broadcasts to " + uid
- + " until " + d.deferUntil);
- }
- // where it goes depends on whether it is coming into an alarm-related situation
- if (d.alarmCount == 0) {
- // common case, put it in the ordinary priority queue
- insertLocked(mDeferredBroadcasts, d);
- scheduleDeferralCheckLocked(true);
- } else {
- // alarm-related: strict order-encountered
- mAlarmDeferrals.add(d);
- }
- } else {
- // We're already deferring, but something was slow again. Reset the
- // deferral decay progression.
- d.deferredBy = mConstants.DEFERRAL;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
- + d.deferredBy);
- }
- }
- }
- }
-
- /**
- * Key entry point when a broadcast about to be delivered is instead
- * set aside for deferred delivery
- */
- public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Enqueuing deferred broadcast " + br);
- }
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
- if (d == null) {
- Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
- } else {
- if (br == null) {
- Slog.wtf(TAG, "Deferring null broadcast to " + uid);
- } else {
- br.deferred = true;
- d.add(br);
- }
- }
- }
- }
-
- /**
- * When there are deferred broadcasts, we need to make sure to recheck the
- * dispatch queue when they come due. Alarm-sensitive deferrals get dispatched
- * aggressively, so we only need to use the ordinary deferrals timing to figure
- * out when to recheck.
- */
- public void scheduleDeferralCheckLocked(boolean force) {
- if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
- final Deferrals d = mDeferredBroadcasts.get(0);
- if (!d.broadcasts.isEmpty()) {
- mHandler.removeCallbacks(mScheduleRunnable);
- mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
- mRecheckScheduled = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
- }
- }
- }
- }
-
- /**
- * Cancel all current deferrals; that is, make all currently-deferred broadcasts
- * immediately deliverable. Used by the wait-for-broadcast-idle mechanism.
- */
- public void cancelDeferralsLocked() {
- zeroDeferralTimes(mAlarmDeferrals);
- zeroDeferralTimes(mDeferredBroadcasts);
- }
-
- private static void zeroDeferralTimes(ArrayList<Deferrals> list) {
- final int num = list.size();
- for (int i = 0; i < num; i++) {
- Deferrals d = list.get(i);
- // Safe to do this in-place because it won't break ordering
- d.deferUntil = d.deferredBy = 0;
- }
- }
-
- // ----------------------------------
-
- /**
- * If broadcasts to this uid are being deferred, find the deferrals record about it.
- * @return null if this uid's broadcasts are not being deferred
- */
- private Deferrals findUidLocked(final int uid) {
- // The common case is that they it isn't also an alarm target...
- Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
- // ...but if not there, also check alarm-prioritized deferrals
- if (d == null) {
- d = findUidLocked(uid, mAlarmDeferrals);
- }
- return d;
- }
-
- /**
- * Remove the given deferral record from whichever queue it might be in at present
- * @return true if the deferral was in fact found, false if this made no changes
- */
- private boolean removeDeferral(Deferrals d) {
- boolean didRemove = mDeferredBroadcasts.remove(d);
- if (!didRemove) {
- didRemove = mAlarmDeferrals.remove(d);
- }
- return didRemove;
- }
-
- /**
- * Find the deferrals record for the given uid in the given list
- */
- private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
- final int numElements = list.size();
- for (int i = 0; i < numElements; i++) {
- Deferrals d = list.get(i);
- if (uid == d.uid) {
- return d;
- }
- }
- return null;
- }
-
- /**
- * Pop the next broadcast record from the head of the given deferrals list,
- * if one exists.
- */
- private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
- final Deferrals d = list.get(0);
- return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
- }
-
- /**
- * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
- */
- private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
- // Simple linear search is appropriate here because we expect to
- // have very few entries in the deferral lists (i.e. very few badly-
- // behaving apps currently facing deferral)
- int i;
- final int numElements = list.size();
- for (i = 0; i < numElements; i++) {
- if (d.deferUntil < list.get(i).deferUntil) {
- break;
- }
- }
- list.add(i, d);
- }
-
- /**
- * Calculate a new deferral time based on the previous time. This should decay
- * toward zero, though a small nonzero floor is an option.
- */
- private long calculateDeferral(long previous) {
- return Math.max(mConstants.DEFERRAL_FLOOR,
- (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
- }
-
- // ----------------------------------
-
- @NeverCompile
- boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
- SimpleDateFormat sdf) {
- final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
- boolean printed = false;
-
- dumper.setHeading("Currently in flight");
- dumper.setLabel("In-Flight Ordered Broadcast");
- if (mCurrentBroadcast != null) {
- dumper.dump(mCurrentBroadcast);
- } else {
- pw.println(" (null)");
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active alarm broadcasts");
- dumper.setLabel("Active Alarm Broadcast");
- for (BroadcastRecord br : mAlarmQueue) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active ordered broadcasts");
- dumper.setLabel("Active Ordered Broadcast");
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpLocked(dumper);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred ordered broadcasts");
- dumper.setLabel("Deferred Ordered Broadcast");
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpLocked(dumper);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- return printed;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
deleted file mode 100644
index 3c56752d08e3..000000000000
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * Copyright (C) 2012 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.am;
-
-import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ApplicationExitInfo;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.usage.UsageEvents.Event;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserJourneyLogger;
-import com.android.server.pm.UserManagerInternal;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-
-/**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
- */
-public class BroadcastQueueImpl extends BroadcastQueue {
- private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
- final BroadcastConstants mConstants;
-
- /**
- * If true, we can delay broadcasts while waiting services to finish in the previous
- * receiver's process.
- */
- final boolean mDelayBehindServices;
-
- final int mSchedGroup;
-
- /**
- * Lists of all active broadcasts that are to be executed immediately
- * (without waiting for another broadcast to finish). Currently this only
- * contains broadcasts to registered receivers, to avoid spinning up
- * a bunch of processes to execute IntentReceiver components. Background-
- * and foreground-priority broadcasts are queued separately.
- */
- final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
- /**
- * Tracking of the ordered broadcast queue, including deferral policy and alarm
- * prioritization.
- */
- final BroadcastDispatcher mDispatcher;
-
- /**
- * Refcounting for completion callbacks of split/deferred broadcasts. The key
- * is an opaque integer token assigned lazily when a broadcast is first split
- * into multiple BroadcastRecord objects.
- */
- final SparseIntArray mSplitRefcounts = new SparseIntArray();
- private int mNextToken = 0;
-
- /**
- * Set when we current have a BROADCAST_INTENT_MSG in flight.
- */
- boolean mBroadcastsScheduled = false;
-
- /**
- * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
- */
- boolean mPendingBroadcastTimeoutMessage;
-
- /**
- * Intent broadcasts that we have tried to start, but are
- * waiting for the application's process to be created. We only
- * need one per scheduling class (instead of a list) because we always
- * process broadcasts one at a time, so no others can be started while
- * waiting for this one.
- */
- BroadcastRecord mPendingBroadcast = null;
-
- /**
- * The receiver index that is pending, to restart the broadcast if needed.
- */
- int mPendingBroadcastRecvIndex;
-
- static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
- static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
- // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
- boolean mLogLatencyMetrics = true;
-
- final BroadcastHandler mHandler;
-
- private final class BroadcastHandler extends Handler {
- public BroadcastHandler(Looper looper) {
- super(looper, null);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case BROADCAST_INTENT_MSG: {
- if (DEBUG_BROADCAST) Slog.v(
- TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
- + mQueueName + "]");
- processNextBroadcast(true);
- } break;
- case BROADCAST_TIMEOUT_MSG: {
- synchronized (mService) {
- broadcastTimeoutLocked(true);
- }
- } break;
- }
- }
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, boolean allowDelayBehindServices,
- int schedGroup) {
- this(service, handler, name, constants, new BroadcastSkipPolicy(service),
- new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
- BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
- super(service, handler, name, skipPolicy, history);
- mHandler = new BroadcastHandler(handler.getLooper());
- mConstants = constants;
- mDelayBehindServices = allowDelayBehindServices;
- mSchedGroup = schedGroup;
- mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
- }
-
- public void start(ContentResolver resolver) {
- mDispatcher.start();
- mConstants.startObserving(mHandler, resolver);
- }
-
- public boolean isDelayBehindServices() {
- return mDelayBehindServices;
- }
-
- public BroadcastRecord getPendingBroadcastLocked() {
- return mPendingBroadcast;
- }
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mDispatcher.getActiveBroadcastLocked();
- }
-
- public int getPreferredSchedulingGroupLocked(ProcessRecord app) {
- final BroadcastRecord active = getActiveBroadcastLocked();
- if (active != null && active.curApp == app) {
- return mSchedGroup;
- }
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if (pending != null && pending.curApp == app) {
- return mSchedGroup;
- }
- return ProcessList.SCHED_GROUP_UNDEFINED;
- }
-
- public void enqueueBroadcastLocked(BroadcastRecord r) {
- r.applySingletonPolicy(mService);
-
- final boolean replacePending = (r.intent.getFlags()
- & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- // Ordered broadcasts obviously need to be dispatched in serial order,
- // but this implementation expects all manifest receivers to also be
- // dispatched in a serial fashion
- boolean serialDispatch = r.ordered;
- if (!serialDispatch) {
- final int N = (r.receivers != null) ? r.receivers.size() : 0;
- for (int i = 0; i < N; i++) {
- if (r.receivers.get(i) instanceof ResolveInfo) {
- serialDispatch = true;
- break;
- }
- }
- }
-
- if (serialDispatch) {
- final BroadcastRecord oldRecord =
- replacePending ? replaceOrderedBroadcastLocked(r) : null;
- if (oldRecord != null) {
- // Replaced, fire the result-to receiver.
- if (oldRecord.resultTo != null) {
- try {
- oldRecord.mIsReceiverAppRunning = true;
- performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
- oldRecord.intent,
- Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.shareIdentity, oldRecord.userId,
- oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
- oldRecord.resultToApp != null
- ? oldRecord.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + oldRecord.intent, e);
-
- }
- }
- } else {
- enqueueOrderedBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- } else {
- final boolean replaced = replacePending
- && (replaceParallelBroadcastLocked(r) != null);
- // Note: We assume resultTo is null for non-ordered broadcasts.
- if (!replaced) {
- enqueueParallelBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- }
- }
-
- public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mParallelBroadcasts.add(r);
- enqueueBroadcastHelper(r);
- }
-
- public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mDispatcher.enqueueOrderedBroadcastLocked(r);
- enqueueBroadcastHelper(r);
- }
-
- /**
- * Don't call this method directly; call enqueueParallelBroadcastLocked or
- * enqueueOrderedBroadcastLocked.
- */
- private void enqueueBroadcastHelper(BroadcastRecord r) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- }
- }
-
- /**
- * Find the same intent from queued parallel broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
- return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
- }
-
- /**
- * Find the same intent from queued ordered broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
- return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
- BroadcastRecord r, String typeForLogging) {
- final Intent intent = r.intent;
- for (int i = queue.size() - 1; i >= 0; i--) {
- final BroadcastRecord old = queue.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST, "***** DROPPING "
- + typeForLogging + " [" + mQueueName + "]: " + intent);
- }
- queue.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- private final void processCurBroadcastLocked(BroadcastRecord r,
- ProcessRecord app) throws RemoteException {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " for app " + app);
- final IApplicationThread thread = app.getThread();
- if (thread == null) {
- throw new RemoteException();
- }
- if (app.isInFullBackup()) {
- skipReceiverLocked(r);
- return;
- }
-
- r.curApp = app;
- r.curAppLastProcessState = app.mState.getCurProcState();
- final ProcessReceiverRecord prr = app.mReceivers;
- prr.addCurReceiver(r);
- app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- // Don't bump its LRU position if it's in the background restricted.
- if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
- < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
- mService.updateLruProcessLocked(app, false, null);
- }
- // Make sure the oom adj score is updated before delivering the broadcast.
- // Force an update, even if there are other pending requests, overall it still saves time,
- // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
- mService.enqueueOomAdjTargetLocked(app);
- mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
- // Tell the application to launch this receiver.
- maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
- r.intent.setComponent(r.curComponent);
-
- // See if we need to delay the freezer based on BroadcastOptions
- if (r.options != null
- && r.options.getTemporaryAppAllowlistDuration() > 0
- && r.options.getTemporaryAppAllowlistType()
- == TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
- r.options.getTemporaryAppAllowlistDuration());
- }
-
- boolean started = false;
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Delivering to component " + r.curComponent
- + ": " + r);
- mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
- PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- final boolean assumeDelivered = false;
- thread.scheduleReceiver(
- prepareReceiverIntent(r.intent, r.curFilteredExtras),
- r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
- r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
- app.mState.getReportedProcState(),
- r.shareIdentity ? r.callerPackage : null);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " DELIVERED for app " + app);
- started = true;
- } finally {
- if (!started) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + ": NOT STARTED!");
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- prr.removeCurReceiver(r);
- }
- }
-
- // if something bad happens here, launch the app and try again
- if (app.isKilled()) {
- throw new RemoteException("app gets killed during broadcasting");
- }
- }
-
- /**
- * Called by ActivityManagerService to notify that the uid has process started, if there is any
- * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
- scheduleBroadcastsLocked();
- }
-
- public boolean onApplicationAttachedLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- updateUidReadyForBootCompletedBroadcastLocked(app.uid);
-
- if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- return sendPendingBroadcastsLocked(app);
- } else {
- return false;
- }
- }
-
- public void onApplicationTimeoutLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationProblemLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationCleanupLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onProcessFreezableChangedLocked(ProcessRecord app) {
- // Not supported; ignore
- }
-
- public boolean sendPendingBroadcastsLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- boolean didSomething = false;
- final BroadcastRecord br = mPendingBroadcast;
- if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
- if (br.curApp != app) {
- Slog.e(TAG, "App mismatch when sending pending broadcast to "
- + app.processName + ", intended target is " + br.curApp.processName);
- return false;
- }
- try {
- mPendingBroadcast = null;
- br.mIsReceiverAppRunning = false;
- processCurBroadcastLocked(br, app);
- didSomething = true;
- } catch (Exception e) {
- Slog.w(TAG, "Exception in new application when starting receiver "
- + br.curComponent.flattenToShortString(), e);
- logBroadcastReceiverDiscardLocked(br);
- finishReceiverLocked(br, br.resultCode, br.resultData,
- br.resultExtras, br.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- br.state = BroadcastRecord.IDLE;
- throw new BroadcastDeliveryFailedException(e);
- }
- }
- return didSomething;
- }
-
- // Skip the current receiver, if any, that is in flight to the given process
- public boolean skipCurrentOrPendingReceiverLocked(ProcessRecord app) {
- BroadcastRecord r = null;
- final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
- if (curActive != null && curActive.curApp == app) {
- // confirmed: the current active broadcast is to the given app
- r = curActive;
- }
-
- // If the current active broadcast isn't this BUT we're waiting for
- // mPendingBroadcast to spin up the target app, that's what we use.
- if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "[" + mQueueName + "] skip & discard pending app " + r);
- r = mPendingBroadcast;
- }
-
- if (r != null) {
- skipReceiverLocked(r);
- return true;
- } else {
- return false;
- }
- }
-
- private void skipReceiverLocked(BroadcastRecord r) {
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- }
-
- public void scheduleBroadcastsLocked() {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
- + mQueueName + "]: current="
- + mBroadcastsScheduled);
-
- if (mBroadcastsScheduled) {
- return;
- }
- mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
- mBroadcastsScheduled = true;
- }
-
- public BroadcastRecord getMatchingOrderedReceiver(ProcessRecord app) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br == null) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] no active broadcast");
- return null;
- }
- if (br.curApp != app) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] active broadcast " + br.curApp + " doesn't match " + app);
- return null;
- }
- return br;
- }
-
- // > 0 only, no worry about "eventual" recycling
- private int nextSplitTokenLocked() {
- int next = mNextToken + 1;
- if (next <= 0) {
- next = 1;
- }
- mNextToken = next;
- return next;
- }
-
- private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
- // the receiver had run for less than allowed bg activity start timeout,
- // so allow the process to still start activities from bg for some more time
- String msgToken = (app.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then schedule the removal of the token after the extended timeout
- mHandler.postAtTime(() -> {
- synchronized (mService) {
- app.removeBackgroundStartPrivileges(r);
- }
- }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
- }
-
- public boolean finishReceiverLocked(ProcessRecord app, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final BroadcastRecord r = getMatchingOrderedReceiver(app);
- if (r != null) {
- return finishReceiverLocked(r, resultCode,
- resultData, resultExtras, resultAbort, waitForServices);
- } else {
- return false;
- }
- }
-
- public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final int state = r.state;
- final ActivityInfo receiver = r.curReceiver;
- final long finishTime = SystemClock.uptimeMillis();
- final long elapsed = finishTime - r.receiverTime;
- r.state = BroadcastRecord.IDLE;
- final int curIndex = r.nextReceiver - 1;
-
- final int packageState = r.mWasReceiverAppStopped
- ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
- : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-
- if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
- final Object curReceiver = r.receivers.get(curIndex);
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
- r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- r.intent.getAction(),
- curReceiver instanceof BroadcastFilter
- ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
- : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
- r.mIsReceiverAppRunning
- ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
- : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime,
- finishTime - r.receiverTime,
- packageState,
- r.curApp.info.packageName,
- r.callerPackage,
- r.calculateTypeForLogging(),
- r.getDeliveryGroupPolicy(),
- r.intent.getFlags(),
- BroadcastRecord.getReceiverPriority(curReceiver),
- r.callerProcState,
- r.curAppLastProcessState);
- }
- if (state == BroadcastRecord.IDLE) {
- Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
- }
- if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
- if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
- // if the receiver has run for more than allowed bg activity start timeout,
- // just remove the token for this process now and we're done
- r.curApp.removeBackgroundStartPrivileges(r);
- } else {
- // It gets more time; post the removal to happen at the appropriate moment
- postActivityStartTokenRemoval(r.curApp, r);
- }
- }
- // If we're abandoning this broadcast before any receivers were actually spun up,
- // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
- if (r.nextReceiver > 0) {
- r.terminalTime[r.nextReceiver - 1] = finishTime;
- }
-
- // if this receiver was slow, impose deferral policy on the app. This will kick in
- // when processNextBroadcastLocked() next finds this uid as a receiver identity.
- if (!r.timeoutExempt) {
- // r.curApp can be null if finish has raced with process death - benign
- // edge case, and we just ignore it because we're already cleaning up
- // as expected.
- if (r.curApp != null
- && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
- // Core system packages are exempt from deferral policy
- if (!UserHandle.isCore(r.curApp.uid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
- + " was slow: " + receiver + " br=" + r);
- }
- mDispatcher.startDeferring(r.curApp.uid);
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
- + " receiver was slow but not deferring: "
- + receiver + " br=" + r);
- }
- }
- }
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
- + " is exempt from deferral policy");
- }
- }
-
- r.intent.setComponent(null);
- if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
- r.curApp.mReceivers.removeCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- if (r.curFilter != null) {
- r.curFilter.receiverList.curBroadcast = null;
- }
- r.curFilter = null;
- r.curReceiver = null;
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- r.curFilteredExtras = null;
- r.mWasReceiverAppStopped = false;
- mPendingBroadcast = null;
-
- r.resultCode = resultCode;
- r.resultData = resultData;
- r.resultExtras = resultExtras;
- if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
- r.resultAbort = resultAbort;
- } else {
- r.resultAbort = false;
- }
-
- // If we want to wait behind services *AND* we're finishing the head/
- // active broadcast on its queue
- if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
- && ((BroadcastQueueImpl) r.queue).getActiveBroadcastLocked() == r) {
- ActivityInfo nextReceiver;
- if (r.nextReceiver < r.receivers.size()) {
- Object obj = r.receivers.get(r.nextReceiver);
- nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
- } else {
- nextReceiver = null;
- }
- // Don't do this if the next receive is in the same process as the current one.
- if (receiver == null || nextReceiver == null
- || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
- || !receiver.processName.equals(nextReceiver.processName)) {
- // In this case, we are ready to process the next receiver for the current broadcast,
- // but are on a queue that would like to wait for services to finish before moving
- // on. If there are background services currently starting, then we will go into a
- // special state where we hold off on continuing this broadcast until they are done.
- if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
- Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
- r.state = BroadcastRecord.WAITING_SERVICES;
- return false;
- }
- }
- }
-
- r.curComponent = null;
-
- // We will process the next receiver right now if this is finishing
- // an app receiver (which is always asynchronous) or after we have
- // come back from calling a receiver.
- final boolean doNext = (state == BroadcastRecord.APP_RECEIVE)
- || (state == BroadcastRecord.CALL_DONE_RECEIVE);
- if (doNext) {
- processNextBroadcastLocked(/* fromMsg= */ false, /* skipOomAdj= */ true);
- }
- return doNext;
- }
-
- public void backgroundServicesFinishedLocked(int userId) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br != null) {
- if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
- Slog.i(TAG, "Resuming delayed broadcast");
- br.curComponent = null;
- br.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- }
- }
- }
-
- public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
- Intent intent, int resultCode, String data, Bundle extras,
- boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
- int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay, int priority,
- int receiverProcessState) throws RemoteException {
- // If the broadcaster opted-in to sharing their identity, then expose package visibility for
- // the receiver.
- if (shareIdentity) {
- mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
- UserHandle.getAppId(receiverUid), callingUid, true);
- }
- // Send the intent to the receiver asynchronously using one-way binder calls.
- if (app != null) {
- final IApplicationThread thread = app.getThread();
- if (thread != null) {
- // If we have an app thread, do the call through that so it is
- // correctly ordered with other one-way calls.
- try {
- final boolean assumeDelivered = !ordered;
- thread.scheduleRegisteredReceiver(
- receiver, intent, resultCode,
- data, extras, ordered, sticky, assumeDelivered, sendingUser,
- app.mState.getReportedProcState(),
- shareIdentity ? callingUid : Process.INVALID_UID,
- shareIdentity ? callingPackage : null);
- } catch (RemoteException ex) {
- // Failed to call into the process. It's either dying or wedged. Kill it gently.
- synchronized (mService) {
- final String msg = "Failed to schedule " + intent + " to " + receiver
- + " via " + app + ": " + ex;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- }
- throw ex;
- }
- } else {
- // Application has died. Receiver doesn't exist.
- throw new RemoteException("app.thread must not be null");
- }
- } else {
- receiver.performReceive(intent, resultCode, data, extras, ordered,
- sticky, sendingUser);
- }
- if (!ordered) {
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
- receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
- callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- intent.getAction(),
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- dispatchDelay, receiveDelay, 0 /* finish_delay */,
- SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- app != null ? app.info.packageName : null, callingPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
- priority, r.callerProcState, receiverProcessState);
- }
- }
-
- private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
- BroadcastFilter filter, boolean ordered, int index) {
- boolean skip = mSkipPolicy.shouldSkip(r, filter);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + filter.receiverList.app
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
- return;
- }
-
- r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
- // If this is not being sent as an ordered broadcast, then we
- // don't want to touch the fields that keep track of the current
- // state of ordered broadcasts.
- if (ordered) {
- r.curFilter = filter;
- filter.receiverList.curBroadcast = r;
- r.state = BroadcastRecord.CALL_IN_RECEIVE;
- if (filter.receiverList.app != null) {
- // Bump hosting application to no longer be in background
- // scheduling class. Note that we can't do that if there
- // isn't an app... but we can only be in that case for
- // things that directly call the IActivityManager API, which
- // are already core system stuff so don't matter for this.
- r.curApp = filter.receiverList.app;
- r.curAppLastProcessState = r.curApp.mState.getCurProcState();
- filter.receiverList.app.mReceivers.addCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
- } else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
- }
-
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
- "Delivering to " + filter + " : " + r);
- final boolean isInFullBackup = (filter.receiverList.app != null)
- && filter.receiverList.app.isInFullBackup();
- final boolean isKilled = (filter.receiverList.app != null)
- && filter.receiverList.app.isKilled();
- if (isInFullBackup || isKilled) {
- // Skip delivery if full backup in progress
- // If it's an ordered broadcast, we need to continue to the next receiver.
- if (ordered) {
- skipReceiverLocked(r);
- }
- } else {
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[index] = r.receiverTime;
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
- maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
- performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
- prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
- filter.receiverList.uid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime, filter.getPriority(),
- filter.receiverList.app != null
- ? filter.receiverList.app.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- // parallel broadcasts are fire-and-forget, not bookended by a call to
- // finishReceiverLocked(), so we manage their activity-start token here
- if (filter.receiverList.app != null
- && r.mBackgroundStartPrivileges.allowsAny()
- && !r.ordered) {
- postActivityStartTokenRemoval(filter.receiverList.app, r);
- }
- }
- if (ordered) {
- r.state = BroadcastRecord.CALL_DONE_RECEIVE;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
- // Clean up ProcessRecord state related to this broadcast attempt
- if (filter.receiverList.app != null) {
- filter.receiverList.app.removeBackgroundStartPrivileges(r);
- if (ordered) {
- filter.receiverList.app.mReceivers.removeCurReceiver(r);
- // Something wrong, its oom adj could be downgraded, but not in a hurry.
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- }
- // And BroadcastRecord state related to ordered delivery, if appropriate
- if (ordered) {
- r.curFilter = null;
- filter.receiverList.curBroadcast = null;
- }
- }
- }
-
- void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
- @Nullable BroadcastOptions brOptions) {
- if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
- return;
- }
- long duration = brOptions.getTemporaryAppAllowlistDuration();
- final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
- final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
- final String reason = brOptions.getTemporaryAppAllowlistReason();
-
- if (duration > Integer.MAX_VALUE) {
- duration = Integer.MAX_VALUE;
- }
- // XXX ideally we should pause the broadcast until everything behind this is done,
- // or else we will likely start dispatching the broadcast before we have opened
- // access to the app (there is a lot of asynchronicity behind this). It is probably
- // not that big a deal, however, because the main purpose here is to allow apps
- // to hold wake locks, and they will be able to acquire their wake lock immediately
- // it just won't be enabled until we get through this work.
- StringBuilder b = new StringBuilder();
- b.append("broadcast:");
- UserHandle.formatUid(b, r.callingUid);
- b.append(":");
- if (r.intent.getAction() != null) {
- b.append(r.intent.getAction());
- } else if (r.intent.getComponent() != null) {
- r.intent.getComponent().appendShortString(b);
- } else if (r.intent.getData() != null) {
- b.append(r.intent.getData());
- }
- b.append(",reason:");
- b.append(reason);
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
- + " type=" + type + " : " + b.toString());
- }
-
- // Only add to temp allowlist if it's not the APP_FREEZING_DELAYED type. That will be
- // handled when the broadcast is actually being scheduled on the app thread.
- if (type != TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
- r.callingUid);
- }
- }
-
- private void processNextBroadcast(boolean fromMsg) {
- synchronized (mService) {
- processNextBroadcastLocked(fromMsg, false);
- }
- }
-
- private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
- @Nullable Bundle filteredExtras) {
- final Intent intent = new Intent(originalIntent);
- if (filteredExtras != null) {
- intent.replaceExtras(filteredExtras);
- }
- return intent;
- }
-
- public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
- BroadcastRecord r;
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
- + mQueueName + "]: "
- + mParallelBroadcasts.size() + " parallel broadcasts; "
- + mDispatcher.describeStateLocked());
-
- mService.updateCpuStats();
-
- if (fromMsg) {
- mBroadcastsScheduled = false;
- }
-
- // First, deliver any non-serialized broadcasts right away.
- while (mParallelBroadcasts.size() > 0) {
- r = mParallelBroadcasts.remove(0);
- r.dispatchTime = SystemClock.uptimeMillis();
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
- r.mIsReceiverAppRunning = true;
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
-
- final int N = r.receivers.size();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
- + mQueueName + "] " + r);
- for (int i=0; i<N; i++) {
- Object target = r.receivers.get(i);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering non-ordered on [" + mQueueName + "] to registered "
- + target + ": " + r);
- deliverToRegisteredReceiverLocked(r,
- (BroadcastFilter) target, false, i);
- }
- addBroadcastToHistoryLocked(r);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
- + mQueueName + "] " + r);
- }
-
- // Now take care of the next serialized one...
-
- // If we are waiting for a process to come up to handle the next
- // broadcast, then do nothing at this point. Just in case, we
- // check that the process we're waiting for still exists.
- if (mPendingBroadcast != null) {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "processNextBroadcast [" + mQueueName + "]: waiting for "
- + mPendingBroadcast.curApp);
-
- boolean isDead;
- if (mPendingBroadcast.curApp.getPid() > 0) {
- synchronized (mService.mPidsSelfLocked) {
- ProcessRecord proc = mService.mPidsSelfLocked.get(
- mPendingBroadcast.curApp.getPid());
- isDead = proc == null || proc.mErrorState.isCrashing();
- }
- } else {
- final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
- mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
- isDead = proc == null || !proc.isPendingStart();
- }
- if (!isDead) {
- // It's still alive, so keep waiting
- return;
- } else {
- Slog.w(TAG, "pending app ["
- + mQueueName + "]" + mPendingBroadcast.curApp
- + " died before responding to broadcast");
- mPendingBroadcast.state = BroadcastRecord.IDLE;
- mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
- mPendingBroadcast = null;
- }
- }
-
- boolean looped = false;
-
- do {
- final long now = SystemClock.uptimeMillis();
- r = mDispatcher.getNextBroadcastLocked(now);
-
- if (r == null) {
- // No more broadcasts are deliverable right now, so all done!
- mDispatcher.scheduleDeferralCheckLocked(false);
- synchronized (mService.mAppProfiler.mProfilerLock) {
- mService.mAppProfiler.scheduleAppGcsLPf();
- }
- if (looped && !skipOomAdj) {
- // If we had finished the last ordered broadcast, then
- // make sure all processes have correct oom and sched
- // adjustments.
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
-
- // when we have no more ordered broadcast on this queue, stop logging
- if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
- mLogLatencyMetrics = false;
- }
-
- return;
- }
-
- boolean forceReceive = false;
-
- // Ensure that even if something goes awry with the timeout
- // detection, we catch "hung" broadcasts here, discard them,
- // and continue to make progress.
- //
- // This is only done if the system is ready so that early-stage receivers
- // don't get executed with timeouts; and of course other timeout-
- // exempt broadcasts are ignored.
- int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
- if ((numReceivers > 0) &&
- (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
- Slog.w(TAG, "Hung broadcast ["
- + mQueueName + "] discarded after timeout failure:"
- + " now=" + now
- + " dispatchTime=" + r.dispatchTime
- + " startTime=" + r.receiverTime
- + " intent=" + r.intent
- + " numReceivers=" + numReceivers
- + " nextReceiver=" + r.nextReceiver
- + " state=" + r.state);
- broadcastTimeoutLocked(false); // forcibly finish this broadcast
- forceReceive = true;
- r.state = BroadcastRecord.IDLE;
- }
- }
-
- if (r.state != BroadcastRecord.IDLE) {
- if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
- "processNextBroadcast("
- + mQueueName + ") called when not idle (state="
- + r.state + ")");
- return;
- }
-
- // Is the current broadcast is done for any reason?
- if (r.receivers == null || r.nextReceiver >= numReceivers
- || r.resultAbort || forceReceive) {
- // Send the final result if requested
- if (r.resultTo != null) {
- boolean sendResult = true;
-
- // if this was part of a split/deferral complex, update the refcount and only
- // send the completion when we clear all of them
- if (r.splitToken != 0) {
- int newCount = mSplitRefcounts.get(r.splitToken) - 1;
- if (newCount == 0) {
- // done! clear out this record's bookkeeping and deliver
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Sending broadcast completion for split token "
- + r.splitToken + " : " + r.intent.getAction());
- }
- mSplitRefcounts.delete(r.splitToken);
- } else {
- // still have some split broadcast records in flight; update refcount
- // and hold off on the callback
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Result refcount now " + newCount + " for split token "
- + r.splitToken + " : " + r.intent.getAction()
- + " - not sending completion yet");
- }
- sendResult = false;
- mSplitRefcounts.put(r.splitToken, newCount);
- }
- }
- if (sendResult) {
- if (r.callerApp != null) {
- mService.mOomAdjuster.unfreezeTemporarily(
- r.callerApp,
- CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
- }
- try {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
- + r.intent.getAction() + " app=" + r.callerApp);
- }
- if (r.dispatchTime == 0) {
- // The dispatch time here could be 0, in case it's a parallel
- // broadcast but it has a result receiver. Set it to now.
- r.dispatchTime = now;
- }
- r.mIsReceiverAppRunning = true;
- performReceiveLocked(r, r.resultToApp, r.resultTo,
- new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false, r.shareIdentity,
- r.userId, r.callingUid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime, 0,
- r.resultToApp != null
- ? r.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- logBootCompletedBroadcastCompletionLatencyIfPossible(r);
- // Set this to null so that the reference
- // (local and remote) isn't kept in the mBroadcastHistory.
- r.resultTo = null;
- } catch (RemoteException e) {
- r.resultTo = null;
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + r.intent, e);
- }
- }
- }
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
- cancelBroadcastTimeoutLocked();
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Finished with ordered broadcast " + r);
-
- // ... and on to the next...
- addBroadcastToHistoryLocked(r);
- if (r.intent.getComponent() == null && r.intent.getPackage() == null
- && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- // This was an implicit broadcast... let's record it for posterity.
- mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
- r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
- }
- mDispatcher.retireBroadcastLocked(r);
- r = null;
- looped = true;
- continue;
- }
-
- // Check whether the next receiver is under deferral policy, and handle that
- // accordingly. If the current broadcast was already part of deferred-delivery
- // tracking, we know that it must now be deliverable as-is without re-deferral.
- if (!r.deferred) {
- final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
- if (mDispatcher.isDeferringLocked(receiverUid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
- + " at " + r.nextReceiver + " is under deferral");
- }
- // If this is the only (remaining) receiver in the broadcast, "splitting"
- // doesn't make sense -- just defer it as-is and retire it as the
- // currently active outgoing broadcast.
- BroadcastRecord defer;
- if (r.nextReceiver + 1 == numReceivers) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Sole receiver of " + r
- + " is under deferral; setting aside and proceeding");
- }
- defer = r;
- mDispatcher.retireBroadcastLocked(r);
- } else {
- // Nontrivial case; split out 'uid's receivers to a new broadcast record
- // and defer that, then loop and pick up continuing delivery of the current
- // record (now absent those receivers).
-
- // The split operation is guaranteed to match at least at 'nextReceiver'
- defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Post split:");
- Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
- for (int i = 0; i < r.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + r.receivers.get(i));
- }
- Slog.i(TAG_BROADCAST, "Split receivers:");
- for (int i = 0; i < defer.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i));
- }
- }
- // Track completion refcount as well if relevant
- if (r.resultTo != null) {
- int token = r.splitToken;
- if (token == 0) {
- // first split of this record; refcount for 'r' and 'deferred'
- r.splitToken = defer.splitToken = nextSplitTokenLocked();
- mSplitRefcounts.put(r.splitToken, 2);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Broadcast needs split refcount; using new token "
- + r.splitToken);
- }
- } else {
- // new split from an already-refcounted situation; increment count
- final int curCount = mSplitRefcounts.get(token);
- if (DEBUG_BROADCAST_DEFERRAL) {
- if (curCount == 0) {
- Slog.wtf(TAG_BROADCAST,
- "Split refcount is zero with token for " + r);
- }
- }
- mSplitRefcounts.put(token, curCount + 1);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "New split count for token " + token
- + " is " + (curCount + 1));
- }
- }
- }
- }
- mDispatcher.addDeferredBroadcast(receiverUid, defer);
- r = null;
- looped = true;
- continue;
- }
- }
- } while (r == null);
-
- // Get the next receiver...
- int recIdx = r.nextReceiver++;
-
- // Keep track of when this receiver started, and make sure there
- // is a timeout message pending to kill it if need be.
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[recIdx] = r.receiverTime;
- if (recIdx == 0) {
- r.dispatchTime = r.receiverTime;
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
-
- if (mLogLatencyMetrics) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
- r.dispatchClockTime - r.enqueueClockTime);
- }
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
- + mQueueName + "] " + r);
- }
- if (! mPendingBroadcastTimeoutMessage) {
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Submitting BROADCAST_TIMEOUT_MSG ["
- + mQueueName + "] for " + r + " at " + timeoutTime);
- setBroadcastTimeoutLocked(timeoutTime);
- }
-
- final BroadcastOptions brOptions = r.options;
- final Object nextReceiver = r.receivers.get(recIdx);
-
- if (nextReceiver instanceof BroadcastFilter) {
- // Simple case: this is a registered receiver who gets
- // a direct call.
- BroadcastFilter filter = (BroadcastFilter)nextReceiver;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering ordered ["
- + mQueueName + "] to registered "
- + filter + ": " + r);
- r.mIsReceiverAppRunning = true;
- deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
- if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
- // The receiver has already finished, so schedule to
- // process the next one.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
- + mQueueName + "]: ordered=" + r.ordered
- + " curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver);
- r.state = BroadcastRecord.IDLE;
- scheduleBroadcastsLocked();
- } else {
- if (filter.receiverList != null) {
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- // r is guaranteed ordered at this point, so we know finishReceiverLocked()
- // will get a callback and handle the activity start token lifecycle.
- }
- }
- return;
- }
-
- // Hard case: need to instantiate the receiver, possibly
- // starting its application process to host it.
-
- final ResolveInfo info =
- (ResolveInfo)nextReceiver;
- final ComponentName component = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- final int receiverUid = info.activityInfo.applicationInfo.uid;
-
- final String targetProcess = info.activityInfo.processName;
- final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid);
-
- boolean skip = mSkipPolicy.shouldSkip(r, info);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + info.activityInfo.packageName + " / " + receiverUid
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Skipping delivery of ordered [" + mQueueName + "] "
- + r + " for reason described above");
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
- r.curFilter = null;
- r.state = BroadcastRecord.IDLE;
- r.manifestSkipCount++;
- scheduleBroadcastsLocked();
- return;
- }
- r.manifestCount++;
-
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
- r.state = BroadcastRecord.APP_RECEIVE;
- r.curComponent = component;
- r.curReceiver = info.activityInfo;
- r.curFilteredExtras = filteredExtras;
- if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
- Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
- + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
- + receiverUid);
- }
- final boolean isActivityCapable =
- (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
- maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
- // Report that a component is used for explicit broadcasts.
- if (r.intent.getComponent() != null && r.curComponent != null
- && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
- mService.mUsageStatsService.reportEvent(
- r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
- }
-
- try {
- mService.mPackageManagerInt.notifyComponentUsed(
- r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + r.curComponent.getPackageName() + ": " + e);
- }
-
- // Is this receiver's application already running?
- if (app != null && app.getThread() != null && !app.isKilled()) {
- try {
- app.addPackage(info.activityInfo.packageName,
- info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
- maybeAddBackgroundStartPrivileges(app, r);
- r.mIsReceiverAppRunning = true;
- processCurBroadcastLocked(r, app);
- return;
- } catch (RemoteException e) {
- final String msg = "Failed to schedule " + r.intent + " to " + info
- + " via " + app + ": " + e;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- } catch (RuntimeException e) {
- Slog.wtf(TAG, "Failed sending broadcast to "
- + r.curComponent + " with " + r.intent, e);
- // If some unexpected exception happened, just skip
- // this broadcast. At this point we are not in the call
- // from a client, so throwing an exception out from here
- // will crash the entire system instead of just whoever
- // sent the broadcast.
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- // If a dead object exception was thrown -- fall through to
- // restart the application.
- }
-
- // Registered whether we're bringing this package out of a stopped state
- r.mWasReceiverAppStopped =
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
- // Not running -- get it started, to be executed when the app comes up.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Need to start app ["
- + mQueueName + "] " + targetProcess + " for broadcast " + r);
- r.curApp = mService.startProcessLocked(targetProcess,
- info.activityInfo.applicationInfo, true,
- r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), r.getHostingRecordTriggerType()),
- isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
- (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- if (r.curApp == null) {
- // Ah, this recipient is unavailable. Finish it if necessary,
- // and mark the broadcast record as ready for the next.
- Slog.w(TAG, "Unable to launch app "
- + info.activityInfo.applicationInfo.packageName + "/"
- + receiverUid + " for broadcast "
- + r.intent + ": process is bad");
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- maybeAddBackgroundStartPrivileges(r.curApp, r);
- mPendingBroadcast = r;
- mPendingBroadcastRecvIndex = recIdx;
- }
-
- @Nullable
- private String getTargetPackage(BroadcastRecord r) {
- if (r.intent == null) {
- return null;
- }
- if (r.intent.getPackage() != null) {
- return r.intent.getPackage();
- } else if (r.intent.getComponent() != null) {
- return r.intent.getComponent().getPackageName();
- }
- return null;
- }
-
- static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
- // Only log after last receiver.
- // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
- // last BroadcastRecord of the split broadcast which has non-null resultTo.
- final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (r.nextReceiver < numReceivers) {
- return;
- }
- final String action = r.intent.getAction();
- int event = 0;
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
- }
- if (event != 0) {
- final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
- final int completeLatency = (int)
- (SystemClock.uptimeMillis() - r.enqueueTime);
- final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
- final int completeRealLatency = (int)
- (SystemClock.elapsedRealtime() - r.enqueueRealTime);
- int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
- // This method is called very infrequently, no performance issue we call
- // LocalServices.getService() here.
- final UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- final UserInfo userInfo =
- (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
- if (userInfo != null) {
- userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
- }
- Slog.i(TAG_BROADCAST,
- "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
- + action
- + " dispatchLatency:" + dispatchLatency
- + " completeLatency:" + completeLatency
- + " dispatchRealLatency:" + dispatchRealLatency
- + " completeRealLatency:" + completeRealLatency
- + " receiversSize:" + numReceivers
- + " userId:" + r.userId
- + " userType:" + (userInfo != null? userInfo.userType : null));
- FrameworkStatsLog.write(
- BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
- event,
- dispatchLatency,
- completeLatency,
- dispatchRealLatency,
- completeRealLatency,
- r.userId,
- userType);
- }
- }
-
- private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
- if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
- return;
- }
- final String targetPackage = getTargetPackage(r);
- // Ignore non-explicit broadcasts
- if (targetPackage == null) {
- return;
- }
- mService.mUsageStatsService.reportBroadcastDispatched(
- r.callingUid, targetPackage, UserHandle.of(r.userId),
- r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
- mService.getUidStateLocked(targetUid));
- }
-
- private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
- if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
- return;
- }
- String msgToken = (proc.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then add the token
- proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
- }
-
- final void setBroadcastTimeoutLocked(long timeoutTime) {
- if (! mPendingBroadcastTimeoutMessage) {
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
- mHandler.sendMessageAtTime(msg, timeoutTime);
- mPendingBroadcastTimeoutMessage = true;
- }
- }
-
- final void cancelBroadcastTimeoutLocked() {
- if (mPendingBroadcastTimeoutMessage) {
- mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
- mPendingBroadcastTimeoutMessage = false;
- }
- }
-
- final void broadcastTimeoutLocked(boolean fromMsg) {
- if (fromMsg) {
- mPendingBroadcastTimeoutMessage = false;
- }
-
- if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
- try {
- long now = SystemClock.uptimeMillis();
- BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
- if (fromMsg) {
- if (!mService.mProcessesReady) {
- // Only process broadcast timeouts if the system is ready; some early
- // broadcasts do heavy work setting up system facilities
- return;
- }
-
- // If the broadcast is generally exempt from timeout tracking, we're done
- if (r.timeoutExempt) {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
- + r.intent.getAction());
- }
- return;
- }
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (timeoutTime > now) {
- // We can observe premature timeouts because we do not cancel and reset the
- // broadcast timeout message after each receiver finishes. Instead, we set up
- // an initial timeout then kick it down the road a little further as needed
- // when it expires.
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST,
- "Premature timeout ["
- + mQueueName + "] @ " + now
- + ": resetting BROADCAST_TIMEOUT_MSG for "
- + timeoutTime);
- }
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
- }
-
- if (r.state == BroadcastRecord.WAITING_SERVICES) {
- // In this case the broadcast had already finished, but we had decided to wait
- // for started services to finish as well before going on. So if we have actually
- // waited long enough time timeout the broadcast, let's give up on the whole thing
- // and just move on to the next.
- Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
- ? r.curComponent.flattenToShortString() : "(null)"));
- r.curComponent = null;
- r.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- return;
- }
-
- // If the receiver app is being debugged we quietly ignore unresponsiveness, just
- // tidying up and moving on to the next broadcast without crashing or ANRing this
- // app just because it's stopped at a breakpoint.
- final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
- long timeoutDurationMs = now - r.receiverTime;
- Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
- + "ms ago");
- r.receiverTime = now;
- if (!debugging) {
- r.anrCount++;
- }
-
- ProcessRecord app = null;
- Object curReceiver;
- if (r.nextReceiver > 0) {
- curReceiver = r.receivers.get(r.nextReceiver - 1);
- r.delivery[r.nextReceiver - 1] = BroadcastRecord.DELIVERY_TIMEOUT;
- } else {
- curReceiver = r.curReceiver;
- }
- Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
- logBroadcastReceiverDiscardLocked(r);
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
- timeoutDurationMs);
- if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- if (bf.receiverList.pid != 0
- && bf.receiverList.pid != ActivityManagerService.MY_PID) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockStarted();
- synchronized (mService.mPidsSelfLocked) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockEnded();
- app = mService.mPidsSelfLocked.get(
- bf.receiverList.pid);
- }
- }
- } else {
- app = r.curApp;
- }
-
- if (mPendingBroadcast == r) {
- mPendingBroadcast = null;
- }
-
- // Move on to the next receiver.
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
-
- // The ANR should only be triggered if we have a process record (app is non-null)
- if (!debugging && app != null) {
- mService.appNotResponding(app, timeoutRecord);
- }
-
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
-
- }
-
- private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
- if (original.callingUid < 0) {
- // This was from a registerReceiver() call; ignore it.
- return;
- }
- original.finishTime = SystemClock.uptimeMillis();
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(original));
- }
-
- mService.notifyBroadcastFinishedLocked(original);
- mHistory.addBroadcastToHistoryLocked(original);
- }
-
- public boolean cleanupDisabledPackageReceiversLocked(
- String packageName, Set<String> filterByClasses, int userId) {
- boolean didSomething = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, true);
- }
-
- didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, true);
-
- return didSomething;
- }
-
- final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
- final int logIndex = r.nextReceiver - 1;
- if (logIndex >= 0 && logIndex < r.receivers.size()) {
- Object curReceiver = r.receivers.get(logIndex);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
- bf.owningUserId, System.identityHashCode(r),
- r.intent.getAction(), logIndex, System.identityHashCode(bf));
- } else {
- ResolveInfo ri = (ResolveInfo) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
- System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
- }
- } else {
- if (logIndex < 0) Slog.w(TAG,
- "Discarding broadcast before first receiver is invoked: " + r);
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- -1, System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver,
- "NONE");
- }
- }
-
- private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
- return formatSimple("Broadcast %s from %s (%s) %s",
- state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
- record.callerPackage == null ? "" : record.callerPackage,
- record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
- record.intent == null ? "" : record.intent.getAction());
- }
-
- public boolean isIdleLocked() {
- return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
- && (mPendingBroadcast == null);
- }
-
- public boolean isBeyondBarrierLocked(long barrierTime) {
- // If nothing active, we're beyond barrier
- if (isIdleLocked()) return true;
-
- // Check if parallel broadcasts are beyond barrier
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
-
- // Check if pending broadcast is beyond barrier
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && pending.enqueueTime <= barrierTime) {
- return false;
- }
-
- return mDispatcher.isBeyondBarrier(barrierTime);
- }
-
- public boolean isDispatchedLocked(Intent intent) {
- if (isIdleLocked()) return true;
-
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
- return false;
- }
- }
-
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && intent.filterEquals(pending.intent)) {
- return false;
- }
-
- return mDispatcher.isDispatched(intent);
- }
-
- public void waitForIdle(PrintWriter pw) {
- waitFor(() -> isIdleLocked(), pw, "idle");
- }
-
- public void waitForBarrier(PrintWriter pw) {
- final long barrierTime = SystemClock.uptimeMillis();
- waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
- }
-
- public void waitForDispatched(Intent intent, PrintWriter pw) {
- waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
- }
-
- private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
- long lastPrint = 0;
- while (true) {
- synchronized (mService) {
- if (condition.getAsBoolean()) {
- final String msg = "Queue [" + mQueueName + "] reached " + conditionName
- + " condition";
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- return;
- }
- }
-
- // Print at most every second
- final long now = SystemClock.uptimeMillis();
- if (now >= lastPrint + 1000) {
- lastPrint = now;
- final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
- + " condition; state is " + describeStateLocked();
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- }
-
- // Push through any deferrals to try meeting our condition
- cancelDeferrals();
- SystemClock.sleep(100);
- }
- }
-
- // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
- // be immediately deliverable.
- public void cancelDeferrals() {
- synchronized (mService) {
- mDispatcher.cancelDeferralsLocked();
- scheduleBroadcastsLocked();
- }
- }
-
- public String describeStateLocked() {
- return mParallelBroadcasts.size() + " parallel; "
- + mDispatcher.describeStateLocked();
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- long token = proto.start(fieldId);
- proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
- int N;
- N = mParallelBroadcasts.size();
- for (int i = N - 1; i >= 0; i--) {
- mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
- }
- mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
- }
- mHistory.dumpDebug(proto);
- proto.end(token);
- }
-
- @NeverCompile
- public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
- String dumpPackage, boolean needSep) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
- || mPendingBroadcast != null) {
- boolean printed = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- BroadcastRecord br = mParallelBroadcasts.get(i);
- if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- printed = true;
- pw.println(" Active broadcasts [" + mQueueName + "]:");
- }
- pw.println(" Active Broadcast " + mQueueName + " #" + i + ":");
- br.dump(pw, " ", sdf);
- }
-
- mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
- if (dumpPackage == null || (mPendingBroadcast != null
- && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
- pw.println();
- pw.println(" Pending broadcast [" + mQueueName + "]:");
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dump(pw, " ", sdf);
- } else {
- pw.println(" (null)");
- }
- needSep = true;
- }
- }
- if (dumpConstants) {
- mConstants.dump(new IndentingPrintWriter(pw));
- }
- if (dumpHistory) {
- needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
- }
- return needSep;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 98263df3c4e8..4422608a8893 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -20,6 +20,9 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
@@ -59,6 +62,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Handler;
@@ -85,9 +89,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.AnrTimer;
import dalvik.annotation.optimization.NeverCompile;
@@ -2120,7 +2127,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.nextReceiver = r.receivers.size();
mHistory.onBroadcastFinishedLocked(r);
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2216,6 +2223,59 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return null;
}
+ private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+ // Only log after last receiver.
+ // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+ // last BroadcastRecord of the split broadcast which has non-null resultTo.
+ final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+ if (r.nextReceiver < numReceivers) {
+ return;
+ }
+ final String action = r.intent.getAction();
+ int event = 0;
+ if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+ }
+ if (event != 0) {
+ final int dispatchLatency = (int) (r.dispatchTime - r.enqueueTime);
+ final int completeLatency = (int) (SystemClock.uptimeMillis() - r.enqueueTime);
+ final int dispatchRealLatency = (int) (r.dispatchRealTime - r.enqueueRealTime);
+ final int completeRealLatency = (int)
+ (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+ int userType =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+ // This method is called very infrequently, no performance issue we call
+ // LocalServices.getService() here.
+ final UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ final UserInfo userInfo =
+ (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
+ if (userInfo != null) {
+ userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
+ }
+ Slog.i(TAG, "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+ + action
+ + " dispatchLatency:" + dispatchLatency
+ + " completeLatency:" + completeLatency
+ + " dispatchRealLatency:" + dispatchRealLatency
+ + " completeRealLatency:" + completeRealLatency
+ + " receiversSize:" + numReceivers
+ + " userId:" + r.userId
+ + " userType:" + (userInfo != null ? userInfo.userType : null));
+ FrameworkStatsLog.write(
+ BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+ event,
+ dispatchLatency,
+ completeLatency,
+ dispatchRealLatency,
+ completeRealLatency,
+ r.userId,
+ userType);
+ }
+ }
+
@Override
@NeverCompile
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 4ef31bff9a42..f2b9b25d6097 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.Nullable;
import android.app.IServiceConnection;
@@ -37,7 +38,7 @@ import java.io.PrintWriter;
/**
* Description of a single binding to a service.
*/
-final class ConnectionRecord {
+final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
final AppBindRecord binding; // The application/service binding.
final ActivityServiceConnectionsHolder<ConnectionRecord> activity; // If non-null, the owning activity.
final IServiceConnection conn; // The client connection.
@@ -127,6 +128,14 @@ final class ConnectionRecord {
aliasComponent = _aliasComponent;
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeServiceHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public long getFlags() {
return flags;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 825b938cdafa..3988277ab525 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.UserIdInt;
import android.os.Binder;
@@ -32,7 +33,8 @@ import com.android.internal.app.procstats.ProcessStats;
/**
* Represents a link between a content provider and client.
*/
-public final class ContentProviderConnection extends Binder {
+public final class ContentProviderConnection extends Binder implements
+ OomAdjusterModernImpl.Connection {
public final ContentProviderRecord provider;
public final ProcessRecord client;
public final String clientPackage;
@@ -72,6 +74,14 @@ public final class ContentProviderConnection extends Binder {
createTime = SystemClock.elapsedRealtime();
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeProviderHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public void startAssociationIfNeeded() {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 31328ae83f71..cd6964ea2631 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -587,7 +587,7 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- private void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
@@ -913,7 +913,7 @@ public class OomAdjuster {
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -941,7 +941,7 @@ public class OomAdjuster {
* must have called {@link collectReachableProcessesLocked} on it.
*/
@GuardedBy({"mService", "mProcLock"})
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
final boolean fullUpdate = processes == null;
@@ -1775,12 +1775,11 @@ public class OomAdjuster {
state.setAdjSeq(mAdjSeq);
state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+ state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
state.setCurAdj(CACHED_APP_MAX_ADJ);
state.setCurRawAdj(CACHED_APP_MAX_ADJ);
state.setCompletedAdjSeq(state.getAdjSeq());
state.setCurCapability(PROCESS_CAPABILITY_NONE);
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
return false;
}
@@ -1843,8 +1842,6 @@ public class OomAdjuster {
state.setCurRawProcState(state.getCurProcState());
state.setCurAdj(state.getMaxAdj());
state.setCompletedAdjSeq(state.getAdjSeq());
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
// if curAdj is less than prevAppAdj, then this process was promoted
return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
}
@@ -2561,7 +2558,7 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+ public boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
boolean couldRecurse, boolean dryRun) {
@@ -2991,7 +2988,11 @@ public class OomAdjuster {
return updated;
}
- protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+ /**
+ * Computes the impact on {@code app} the provider connections from {@code client} has.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ public boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
int cachedAdj, boolean couldRecurse, boolean dryRun) {
@@ -3572,7 +3573,7 @@ public class OomAdjuster {
int initialCapability = PROCESS_CAPABILITY_NONE;
boolean initialCached = true;
final ProcessStateRecord state = app.mState;
- final int prevProcState = state.getCurRawProcState();
+ final int prevProcState = state.getCurProcState();
final int prevAdj = state.getCurRawAdj();
// If the process has been marked as foreground, it is starting as the top app (with
// Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1bf779adcce1..dd75bc0442d0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -38,7 +38,6 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
@@ -67,8 +66,10 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.content.pm.ServiceInfo;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,6 +81,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -275,6 +277,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
}
mLastNode = new ProcessRecordNode[size];
+ resetLastNodes();
}
int size() {
@@ -285,7 +288,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
void reset() {
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mProcessRecordNodes[i].reset();
- mLastNode[i] = null;
+ setLastNodeToHead(i);
}
}
@@ -509,26 +512,118 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
/**
- * A helper consumer for collecting processes that have not been reached yet. To avoid object
- * allocations every OomAdjuster update, the results will be stored in
- * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
- * for setting it before usage, as well as, clearing the reachable state of each process in the
- * list.
+ * A {@link Connection} represents any connection between two processes that can cause a
+ * change in importance in the host process based on the client process and connection state.
*/
- private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
- public ArrayList<ProcessRecord> processList = null;
+ public interface Connection {
+ /**
+ * Compute the impact this connection has on the host's importance values.
+ */
+ void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
+ long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+ }
+
+ /**
+ * A helper consumer for marking and collecting reachable processes.
+ */
+ private static class ReachableCollectingConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ ArrayList<ProcessRecord> mReachables = null;
+
+ public void init(ArrayList<ProcessRecord> reachables) {
+ mReachables = reachables;
+ }
+
@Override
- public void accept(ProcessRecord process) {
- if (process.mState.isReachable()) {
+ public void accept(Connection unused, ProcessRecord host) {
+ if (host.mState.isReachable()) {
return;
}
- process.mState.setReachable(true);
- processList.add(process);
+ host.mState.setReachable(true);
+ mReachables.add(host);
+ }
+ }
+
+ private final ReachableCollectingConsumer mReachableCollectingConsumer =
+ new ReachableCollectingConsumer();
+
+ /**
+ * A helper consumer for computing the importance of a connection from a client.
+ * Connections for clients marked reachable will be ignored.
+ */
+ private class ComputeConnectionIgnoringReachableClientsConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord client) {
+ final ProcessRecord host = args.mApp;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+
+ if (client.mState.isReachable()) return;
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
+ oomAdjReason, UNKNOWN_ADJ);
+ }
+ }
+
+ private final ComputeConnectionIgnoringReachableClientsConsumer
+ mComputeConnectionIgnoringReachableClientsConsumer =
+ new ComputeConnectionIgnoringReachableClientsConsumer();
+
+ /**
+ * A helper consumer for computing host process importance from a connection from a client app.
+ */
+ private class ComputeHostConsumer implements BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord host) {
+ final ProcessRecord client = args.mApp;
+ final int cachedAdj = args.mCachedAdj;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+ final boolean fullUpdate = args.mFullUpdate;
+
+ final int prevProcState = host.mState.getCurProcState();
+ final int prevAdj = host.mState.getCurRawAdj();
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
+ fullUpdate, oomAdjReason, cachedAdj);
+
+ updateProcStateSlotIfNecessary(host, prevProcState);
+ updateAdjSlotIfNecessary(host, prevAdj);
}
}
+ private final ComputeHostConsumer mComputeHostConsumer = new ComputeHostConsumer();
+
+ /**
+ * A helper consumer for computing all connections from an app.
+ */
+ private class ComputeConnectionsConsumer implements Consumer<OomAdjusterArgs> {
+ @Override
+ public void accept(OomAdjusterArgs args) {
+ final ProcessRecord app = args.mApp;
+ final ActiveUids uids = args.mUids;
- private final UnreachedProcessCollector mUnreachedProcessCollector =
- new UnreachedProcessCollector();
+ // This process was updated in some way, mark that it was last calculated this sequence.
+ app.mState.setCompletedAdjSeq(mAdjSeq);
+ if (uids != null) {
+ final UidRecord uidRec = app.getUidRecord();
+
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ }
+ mComputeHostConsumer.args = args;
+ forEachConnectionLSP(app, mComputeHostConsumer);
+ }
+ }
+ private final ComputeConnectionsConsumer mComputeConnectionsConsumer =
+ new ComputeConnectionsConsumer();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids) {
@@ -617,6 +712,12 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
}
+ private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
+
private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
if (app.mState.getCurProcState() != prevProcState) {
final int slot = processStateToSlot(app.mState.getCurProcState());
@@ -627,359 +728,247 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
}
+ private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
+
@Override
- protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ // Clear any pending ones because we are doing a full update now.
+ mPendingProcessSet.clear();
+ mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
- mAdjSeq++;
- final ProcessStateRecord state = app.mState;
- final int oldAdj = state.getCurRawAdj();
- final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
- ? oldAdj : UNKNOWN_ADJ;
+ fullUpdateLSP(oomAdjReason);
- final ActiveUids uids = mTmpUidRecords;
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
-
- uids.clear();
- targetProcesses.clear();
- targetProcesses.add(app);
- reachableProcesses.clear();
-
- // Find out all reachable processes from this app.
- collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
- // Copy all of the reachable processes into the target process set.
- targetProcesses.addAll(reachableProcesses);
- reachableProcesses.clear();
+ @GuardedBy({"mService", "mProcLock"})
+ @Override
+ protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ mPendingProcessSet.add(app);
+ performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
+ return true;
+ }
- final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
- topApp, targetProcesses, uids, false, now, cachedAdj);
+ @GuardedBy("mService")
+ @Override
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
- reachableProcesses.addAll(targetProcesses);
- assignCachedAdjIfNecessary(reachableProcesses);
- for (int i = uids.size() - 1; i >= 0; i--) {
- final UidRecord uidRec = uids.valueAt(i);
- uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
- }
- updateUidsLSP(uids, nowElapsed);
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+ synchronized (mProcLock) {
+ partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
- targetProcesses.clear();
- reachableProcesses.clear();
+ mPendingProcessSet.clear();
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- return result;
}
+ /**
+ * Perform a full update on the entire process list.
+ */
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
- boolean startProfiling) {
- final boolean fullUpdate = processes == null;
- final ArrayList<ProcessRecord> activeProcesses = fullUpdate
- ? mProcessList.getLruProcessesLOSP() : processes;
- ActiveUids activeUids = uids;
- if (activeUids == null) {
- final int numUids = mActiveUids.size();
- activeUids = mTmpUidRecords;
- activeUids.clear();
- for (int i = 0; i < numUids; i++) {
- UidRecord uidRec = mActiveUids.valueAt(i);
- activeUids.put(uidRec.getUid(), uidRec);
- }
- }
-
- if (startProfiling) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
- }
+ private void fullUpdateLSP(@OomAdjReason int oomAdjReason) {
+ final ProcessRecord topApp = mService.getTopApp();
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- final int numProc = activeProcesses.size();
mAdjSeq++;
- if (fullUpdate) {
- mNewNumServiceProcs = 0;
- mNewNumAServiceProcs = 0;
- }
-
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- targetProcesses.clear();
- if (!fullUpdate) {
- targetProcesses.addAll(activeProcesses);
- }
-
- performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
- fullUpdate, now, UNKNOWN_ADJ);
-
- // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
- targetProcesses.clear();
-
- if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- return;
- }
-
- /**
- * Perform the oom adj update on the given {@code targetProcesses}.
- *
- * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
- * must have called {@link collectReachableProcessesLocked} on it.
- */
- private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
- ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
- boolean fullUpdate, long now, int cachedAdj) {
- final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
- clientProcesses.clear();
+ mNewNumServiceProcs = 0;
+ mNewNumAServiceProcs = 0;
- // We'll need to collect the upstream processes of the target apps here, because those
- // processes would potentially impact the procstate/adj via bindings.
- if (!fullUpdate) {
- collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
+ // Clear the priority queues.
+ mProcessRecordProcStateNodes.reset();
+ mProcessRecordAdjNodes.reset();
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
- }
- } else {
- final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
- for (int i = 0, size = lru.size(); i < size; i++) {
- final ProcessRecord app = lru.get(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
+ final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+ for (int i = lru.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = lru.get(i);
+ final int prevProcState = app.mState.getCurProcState();
+ final int prevAdj = app.mState.getCurRawAdj();
+ app.mState.resetCachedInfo();
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
}
+ uidRec.reset();
}
- }
- updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
- cachedAdj, now, fullUpdate);
+ // Compute initial values, the procState and adj priority queues will be populated here.
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
+ false);
+ updateProcStateSlot(app, prevProcState);
+ updateAdjSlot(app, prevAdj);
+ }
- clientProcesses.clear();
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
+ mProcessRecordAdjNodes.resetLastNodes();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
+ computeConnectionsLSP();
- return true;
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
}
/**
- * Collect the client processes from the given {@code apps}, the result will be returned in the
- * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
- * {@code apps}.
+ * Traverse the process graph and update processes based on changes in connection importances.
*/
- @GuardedBy("mService")
- private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
- ArrayList<ProcessRecord> clientProcesses) {
- // Mark all of the provided apps as reachable to avoid including them in the client list.
- final int appsSize = apps.size();
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(true);
- }
-
- clientProcesses.clear();
- mUnreachedProcessCollector.processList = clientProcesses;
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.forEachClient(mUnreachedProcessCollector);
+ @GuardedBy({"mService", "mProcLock"})
+ private void computeConnectionsLSP() {
+ // 1st pass, scan each slot in the procstate node list.
+ for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+ mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
- mUnreachedProcessCollector.processList = null;
- // Reset the temporary bits.
- for (int i = clientProcesses.size() - 1; i >= 0; i--) {
- clientProcesses.get(i).mState.setReachable(false);
- }
- for (int i = 0, size = apps.size(); i < size; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(false);
+ // 2nd pass, scan each slot in the adj node list.
+ for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+ mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
}
+ /**
+ * Perform a partial update on the target processes and their reachable processes.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
- ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
- mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
-
- mProcessRecordProcStateNodes.resetLastNodes();
- mProcessRecordAdjNodes.resetLastNodes();
+ private void partialUpdateLSP(@OomAdjReason int oomAdjReason, ArraySet<ProcessRecord> targets) {
+ final ProcessRecord topApp = mService.getTopApp();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
- final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+ ActiveUids activeUids = mTmpUidRecords;
+ activeUids.clear();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false);
mAdjSeq++;
- // All apps to be updated will be moved to the lowest slot.
- if (fullUpdate) {
- // Move all the process record node to the lowest slot, we'll do recomputation on all of
- // them. Use the processes from the lru list, because the scanning order matters here.
- final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
- for (int i = procStateTarget; i >= 0; i--) {
- mProcessRecordProcStateNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordProcStateNodes.setLastNodeToHead(i);
- }
- // enqueue the targets in the reverse order of the lru list.
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
- }
- // Do the same to the adj nodes.
- for (int i = adjTarget; i >= 0; i--) {
- mProcessRecordAdjNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordAdjNodes.setLastNodeToHead(i);
- }
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
- }
- } else {
- // Move the target processes to the lowest slot.
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
- final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
- mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
- mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
- }
- // Move the "lastNode" to head to make sure we scan all nodes in this slot.
- mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
- mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
- }
- // All apps to be updated have been moved to the lowest slot.
- // Do an initial pass of the computation.
- mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
- this::computeInitialOomAdjLSP);
-
- if (!fullUpdate) {
- // We didn't update the client processes with the computeInitialOomAdjLSP
- // because they don't need to do so. But they'll be playing vital roles in
- // computing the bindings. So include them into the scan list below.
- for (int i = 0, size = clientProcesses.size(); i < size; i++) {
- mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
- }
- // We don't update the adj list since we're resetting it below.
- }
+ final ArrayList<ProcessRecord> reachables = mTmpProcessList;
+ reachables.clear();
- // Now nodes are set into their slots, without factoring in the bindings.
- // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
- //
- // The whole rationale here is that, the bindings from client to host app, won't elevate
- // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
- // is a special case here, but client app's raw adj is still no less than the host app's).
- // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
- // check its bindings, elevate its host app's slot if necessary.
- //
- // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
- // Because the procstate and adj are not always in sync - there are cases where
- // the processes with lower proc state could be getting a higher oom adj score.
- // And because of this, the procstate and adj node lists are basically two priority heaps.
- //
- // As the 2nd pass with the adj node lists potentially includes a significant amount of
- // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
- // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
- // gets bumped, we'll only scan those in 2nd pass.
+ for (int i = 0, size = targets.size(); i < size; i++) {
+ final ProcessRecord target = targets.valueAtUnchecked(i);
+ target.mState.resetCachedInfo();
+ target.mState.setReachable(true);
+ reachables.add(target);
+ }
- mProcessRecordAdjNodes.resetLastNodes();
+ // Collect all processes that are reachable.
+ // Any process not found in this step will not change in importance during this update.
+ collectAndMarkReachableProcessesLSP(reachables);
- // 1st pass, scan each slot in the procstate node list.
- for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
- mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
- }
+ // Initialize the reachable processes based on their own values plus any
+ // connections from processes not found in the previous step. Since those non-reachable
+ // processes cannot change as a part of this update, their current values can be used
+ // right now.
+ mProcessRecordProcStateNodes.resetLastNodes();
+ initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
- // 2nd pass, scan each slot in the adj node list.
- for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
- mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
+ mProcessRecordAdjNodes.resetLastNodes();
+ // Now traverse and compute the connections of processes with changed importance.
+ computeConnectionsLSP();
+
+ boolean unassignedAdj = false;
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessStateRecord state = reachables.get(i).mState;
+ state.setReachable(false);
+ state.setCompletedAdjSeq(mAdjSeq);
+ if (state.getCurAdj() >= UNKNOWN_ADJ) {
+ unassignedAdj = true;
+ }
}
- }
- @GuardedBy({"mService", "mProcLock"})
- private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
- final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
- final ProcessRecord topApp = args.mTopApp;
- final long now = args.mNow;
- final int oomAdjReason = args.mOomAdjReason;
- final ActiveUids uids = args.mUids;
- final boolean fullUpdate = args.mFullUpdate;
-
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "OOM ADJ initial args app=" + app
- + " cachedAdj=" + cachedAdj
- + " topApp=" + topApp
- + " now=" + now
- + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
- + " fullUpdate=" + fullUpdate);
+ // If all processes have an assigned adj, no need to calculate and assign cached adjs.
+ if (unassignedAdj) {
+ // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
}
- if (uids != null) {
- final UidRecord uidRec = app.getUidRecord();
-
- if (uidRec != null) {
- uids.put(uidRec.getUid(), uidRec);
+ // Repopulate any uid record that may have changed.
+ for (int i = 0, size = activeUids.size(); i < size; i++) {
+ final UidRecord ur = activeUids.valueAt(i);
+ ur.reset();
+ for (int j = ur.getNumOfProcs() - 1; j >= 0; j--) {
+ final ProcessRecord proc = ur.getProcessRecordByIndex(j);
+ updateAppUidRecIfNecessaryLSP(proc);
}
}
- computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
- false);
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
}
/**
- * @return The proposed change to the schedGroup.
+ * Mark all processes reachable from the {@code reachables} processes and add them to the
+ * provided {@code reachables} list (targets excluded).
+ *
+ * Returns true if a cycle exists within the reachable process graph.
*/
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
- int schedGroup) {
- schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
-
- updateAdjSlotIfNecessary(app, prevRawAppAdj);
-
- return schedGroup;
+ private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
+ mReachableCollectingConsumer.init(reachables);
+ for (int i = 0; i < reachables.size(); i++) {
+ ProcessRecord pr = reachables.get(i);
+ forEachConnectionLSP(pr, mReachableCollectingConsumer);
+ }
}
- @GuardedBy({"mService", "mProcLock"})
- @Override
- protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
- int prevProcState) {
- super.setIntermediateProcStateLSP(app, procState, prevProcState);
+ /**
+ * Calculate initial importance states for {@code reachables} and update their slot position
+ * if necessary.
+ */
+ private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessRecord reachable = reachables.get(i);
+ final int prevProcState = reachable.mState.getCurProcState();
+ final int prevAdj = reachable.mState.getCurRawAdj();
- updateProcStateSlotIfNecessary(app, prevProcState);
+ args.mApp = reachable;
+ computeOomAdjIgnoringReachablesLSP(args);
+
+ updateProcStateSlot(reachable, prevProcState);
+ updateAdjSlot(reachable, prevAdj);
+ }
}
+ /**
+ * Calculate initial importance states for {@code app}.
+ * Processes not marked reachable cannot change as a part of this update, so connections from
+ * those process can be calculated now.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+ private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
final ProcessRecord topApp = args.mTopApp;
final long now = args.mNow;
final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
- final boolean fullUpdate = args.mFullUpdate;
- final ActiveUids uids = args.mUids;
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
+
+ mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+ forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+ }
+
+ /**
+ * Stream the connections with {@code app} as a client to
+ * {@code connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
final ProcessServiceRecord psr = app.mServices;
for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
ConnectionRecord cr = psr.getConnectionAt(i);
@@ -987,16 +976,14 @@ public class OomAdjusterModernImpl extends OomAdjuster {
? cr.binding.service.isolationHostProc : cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
|| (service.isSdkSandbox && cr.binding.attributedClient != null)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1004,15 +991,13 @@ public class OomAdjusterModernImpl extends OomAdjuster {
final ProcessRecord service = cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
final ProcessProviderRecord ppr = app.mProviders;
@@ -1021,15 +1006,51 @@ public class OomAdjusterModernImpl extends OomAdjuster {
ProcessRecord provider = cpc.provider.proc;
if (provider == null || provider == app
|| (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
- && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
+ connectionConsumer.accept(cpc, provider);
+ }
+ }
+
+ /**
+ * Stream the connections from clients with {@code app} as the host to {@code
+ * connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachClientConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
+ final ProcessServiceRecord psr = app.mServices;
+
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ final ServiceRecord s = psr.getRunningServiceAt(i);
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+ final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+ for (int k = clist.size() - 1; k >= 0; k--) {
+ final ConnectionRecord cr = clist.get(k);
+ final ProcessRecord client;
+ if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+ client = cr.binding.attributedClient;
+ } else {
+ client = cr.binding.client;
+ }
+ connectionConsumer.accept(cr, client);
+ }
+ }
+ }
- computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = ppr.getProviderAt(i);
+ for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+ final ContentProviderConnection conn = cpr.connections.get(j);
+ connectionConsumer.accept(conn, conn.client);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2ef433cad8ce..0aa1a69334d7 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -718,9 +718,7 @@ class ProcessErrorStateRecord {
mService.mContext, mApp.info.packageName, mApp.info.flags);
}
}
- for (BroadcastQueue queue : mService.mBroadcastQueues) {
- queue.onApplicationProblemLocked(mApp);
- }
+ mService.getBroadcastQueue().onApplicationProblemLocked(mApp);
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 89c89944e92e..a1fdd5070527 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -208,7 +208,7 @@ public final class ProcessList {
// Number of levels we have available for different service connection group importance
// levels.
- static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+ public static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7009bd0b4695..7356588b408a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,10 +69,8 @@ import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -1659,34 +1657,4 @@ class ProcessRecord implements WindowProcessListener {
&& !mOptRecord.shouldNotFreeze()
&& mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
}
-
- /**
- * Traverses all client processes and feed them to consumer.
- */
- @GuardedBy("mProcLock")
- void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
- for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
- final ServiceRecord s = mServices.getRunningServiceAt(i);
- final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
- s.getConnections();
- for (int j = serviceConnections.size() - 1; j >= 0; j--) {
- final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
- for (int k = clist.size() - 1; k >= 0; k--) {
- final ConnectionRecord cr = clist.get(k);
- if (isSdkSandbox && cr.binding.attributedClient != null) {
- consumer.accept(cr.binding.attributedClient);
- } else {
- consumer.accept(cr.binding.client);
- }
- }
- }
- }
- for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
- final ContentProviderRecord cpr = mProviders.getProviderAt(i);
- for (int j = cpr.connections.size() - 1; j >= 0; j--) {
- final ContentProviderConnection conn = cpr.connections.get(j);
- consumer.accept(conn.client);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 562beaf50a7f..3d695bcdb767 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -541,7 +541,7 @@ final class ProcessServiceRecord {
private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
final ProcessRecord attributedClient = connection.binding.attributedClient;
if (attributedClient != null && connection.binding.service.isSdkSandbox) {
- if (attributedClient.mServices.mSdkSandboxConnections == null) {
+ if (attributedClient.mServices.mSdkSandboxConnections != null) {
attributedClient.mServices.mSdkSandboxConnections.remove(connection);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 68b4e3fb51ba..7ee2a7ababb3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -149,11 +149,7 @@ public class FaceService extends SystemService {
return proto.getBytes();
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
@@ -297,29 +293,6 @@ public class FaceService extends SystemService {
restricted, statsClient, isKeyguard);
}
- @android.annotation.EnforcePermission(
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
- @Override // Binder call
- public long authenticateInBackground(final IBinder token, final long operationId,
- final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
- // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
- // lockdown, something wrong happened. See similar path in FingerprintService.
-
- super.authenticateInBackground_enforcePermission();
-
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
- if (provider == null) {
- Slog.w(TAG, "Null provider for authenticate");
- return -1;
- }
- options.setSensorId(provider.first);
-
- return provider.second.scheduleAuthenticate(token, operationId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
- false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
- true /* allowBackgroundAuthentication */);
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long detectFace(final IBinder token,
@@ -583,11 +556,7 @@ public class FaceService extends SystemService {
return provider.getEnrolledFaces(sensorId, userId);
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
super.hasEnrolledFaces_enforcePermission();
diff --git a/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
new file mode 100644
index 000000000000..413020eb9bd9
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.clipboard;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+
+import com.android.server.LocalServices;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ArcClipboardMonitor implements Consumer<ClipData> {
+ private static final String TAG = "ArcClipboardMonitor";
+
+ public interface ArcClipboardBridge {
+ /**
+ * Called when a clipboard content is updated.
+ */
+ void onPrimaryClipChanged(ClipData data);
+
+ /**
+ * Passes the callback to set a new clipboard content with a uid.
+ */
+ void setHandler(BiConsumer<ClipData, Integer> setAndroidClipboard);
+ }
+
+ private ArcClipboardBridge mBridge;
+ private BiConsumer<ClipData, Integer> mAndroidClipboardSetter;
+
+ ArcClipboardMonitor(final BiConsumer<ClipData, Integer> setAndroidClipboard) {
+ mAndroidClipboardSetter = setAndroidClipboard;
+ LocalServices.addService(ArcClipboardMonitor.class, this);
+ }
+
+ @Override
+ public void accept(final @Nullable ClipData clip) {
+ if (mBridge != null) {
+ mBridge.onPrimaryClipChanged(clip);
+ }
+ }
+
+ /**
+ * Sets the other end of the clipboard bridge.
+ */
+ public void setClipboardBridge(ArcClipboardBridge bridge) {
+ mBridge = bridge;
+ mBridge.setHandler(mAndroidClipboardSetter);
+ }
+}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 49f607095b90..4c3020f58870 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -151,7 +151,7 @@ public class ClipboardService extends SystemService {
private final ContentCaptureManagerInternal mContentCaptureInternal;
private final AutofillManagerInternal mAutofillInternal;
private final IBinder mPermissionOwner;
- private final Consumer<ClipData> mEmulatorClipboardMonitor;
+ private final Consumer<ClipData> mClipboardMonitor;
private final Handler mWorkerHandler;
@GuardedBy("mLock")
@@ -192,7 +192,7 @@ public class ClipboardService extends SystemService {
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (Build.IS_EMULATOR) {
- mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
+ mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
if (clipboard != null) {
@@ -201,8 +201,12 @@ public class ClipboardService extends SystemService {
}
}
});
+ } else if (Build.IS_ARC) {
+ mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
+ setPrimaryClipInternal(clip, uid);
+ });
} else {
- mEmulatorClipboardMonitor = (clip) -> {};
+ mClipboardMonitor = (clip) -> {};
}
updateConfig();
@@ -937,7 +941,7 @@ public class ClipboardService extends SystemService {
private void setPrimaryClipInternalLocked(
@Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
if (deviceId == DEVICE_ID_DEFAULT) {
- mEmulatorClipboardMonitor.accept(clip);
+ mClipboardMonitor.accept(clip);
}
final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index f50831964303..55601bc5a596 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -8,6 +8,19 @@
}
],
"file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ },
+ {
+ "name":"FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns":[
+ "Vpn\\.java",
+ "VpnIkeV2Utils\\.java",
+ "VpnProfileStore\\.java"
+ ]
}
],
"presubmit-large": [
@@ -26,10 +39,5 @@
],
"file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
- ],
- "postsubmit":[
- {
- "name":"FrameworksVpnTests"
- }
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a9beac..91e560e19f0d 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import android.os.Trace;
/**
@@ -110,7 +109,6 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display
try {
mDisplayOffloader.stopOffload();
mIsActive = false;
- mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2010aca72494..7f014f6dae28 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// information.
// At the time of this writing, this value is changed within updatePowerState() only, which is
// limited to the thread used by DisplayControllerHandler.
- private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ @VisibleForTesting
+ final BrightnessReason mBrightnessReason = new BrightnessReason();
private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
// Brightness animation ramp rates in brightness units per second
@@ -1379,7 +1380,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
- mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
@@ -1434,7 +1435,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
- if (Float.isNaN(brightnessState)) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
@@ -1455,8 +1458,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
}
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} else {
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
}
} else {
@@ -3020,9 +3026,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
setDwbcLoggingEnabled(msg.arg1);
break;
case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
- mDisplayBrightnessController.setBrightnessFromOffload(
- Float.intBitsToFloat(msg.arg1));
- updatePowerState();
+ if (mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1))) {
+ updatePowerState();
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02dbc46df..34d53be6933a 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@ import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@ public final class DisplayBrightnessController {
/**
* Sets the brightness from the offload session.
+ * @return Whether the offload brightness has changed
*/
- public void setBrightnessFromOffload(float brightness) {
+ public boolean setBrightnessFromOffload(float brightness) {
synchronized (mLock) {
- if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+ if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+ && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+ .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
.setOffloadScreenBrightness(brightness);
+ return true;
}
}
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 8e844501ab34..71cc8725f85b 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -133,7 +133,8 @@ public class DisplayBrightnessStrategySelector {
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
- } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+ } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index d1ca49b8bf79..8b54b22082fd 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -108,7 +108,6 @@ public class AutomaticBrightnessStrategy {
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
- && brightnessReason != BrightnessReason.REASON_OFFLOAD
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a23c08a9e5c6..d876a381ca7a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -198,7 +198,7 @@ public class HdmiCecMessageValidator {
addValidationInfo(Constants.MESSAGE_CEC_VERSION,
oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
- new AsciiValidator(3), ADDR_NOT_UNREGISTERED, ADDR_BROADCAST);
+ new AsciiValidator(3), ADDR_TV, ADDR_BROADCAST);
ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
addValidationInfo(Constants.MESSAGE_DECK_CONTROL,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c8c662387d16..d0532b995deb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4665,15 +4665,27 @@ public class HdmiControlService extends SystemService {
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
+ int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
- // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+ // We can't send <Set Audio Volume Level> if the System Audio device doesn't support it.
+ // But AudioService has already updated its volume and expects us to set it.
+ // So the best we can do is to send <Give Audio Status>, which triggers
+ // <Report Audio Status>, which should update AudioService with its correct volume.
if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
!= DeviceFeatures.FEATURE_SUPPORTED) {
+ // Update the volume tracked in AbsoluteVolumeAudioStatusAction
+ // so it correctly processes the next <Report Audio Status>
+ HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+ avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
+ // Send <Give Audio Status>
+ sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress()
+ ));
return;
}
// Send <Set Audio Volume Level> to notify the System Audio device of the volume change
- int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fc994719c85d..0ca48084f4e1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -449,7 +449,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
/**
- * Sends a reliable message rom this client to a nanoapp.
+ * Sends a reliable message from this client to a nanoapp.
*
* @param message the message to send
* @param transactionCallback The callback to use to confirm the delivery of the message for
@@ -473,6 +473,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
@Nullable IContextHubTransactionCallback transactionCallback) {
ContextHubServiceUtil.checkPermissions(mContext);
+ // Clear the isReliable and messageSequenceNumber fields.
+ // These will be set to true and a real value if the message
+ // is reliable.
+ message.setIsReliable(false);
+ message.setMessageSequenceNumber(0);
+
@ContextHubTransaction.Result int result;
if (isRegistered()) {
int authState = mMessageChannelNanoappIdMap.getOrDefault(
@@ -485,7 +491,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
// Return a bland error code for apps targeting old SDKs since they wouldn't be able
// to use an error code added in S.
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } else if (authState == AUTHORIZATION_UNKNOWN) {
+ }
+
+ if (authState == AUTHORIZATION_UNKNOWN) {
// Only check permissions the first time a nanoapp is queried since nanoapp
// permissions don't currently change at runtime. If the host permission changes
// later, that'll be checked by onOpChanged.
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 8db59055c1f4..4fc1a17032e9 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -27,11 +27,15 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
- }
- ],
- "postsubmit":[
+ },
{
- "name":"FrameworksVpnTests"
+ "name": "FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa28632..3ecc58e2aef2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@ abstract public class ManagedServices {
return false;
}
+ protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+ int userId) {
+ if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+ || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+ return false;
+ }
+ return componentHasBindPermission(component, userId);
+ }
+
+ private boolean componentHasBindPermission(ComponentName component, int userId) {
+ ServiceInfo info = getServiceInfo(component, userId);
+ if (info == null) {
+ return false;
+ }
+ return mConfig.bindPermission.equals(info.permission);
+ }
+
boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
synchronized (mApproved) {
ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@ abstract public class ManagedServices {
for (int uid : uidList) {
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
+ trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
}
}
}
@@ -1135,8 +1153,7 @@ abstract public class ManagedServices {
synchronized (mMutex) {
if (enabled) {
- if (isPackageOrComponentAllowed(component.flattenToString(), userId)
- || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(component, userId)) {
registerServiceLocked(component, userId);
} else {
Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@ abstract public class ManagedServices {
return removed;
}
+ private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+ synchronized (mApproved) {
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ return;
+ }
+ for (int i = 0; i < approvedByType.size(); i++) {
+ final ArraySet<String> approved = approvedByType.valueAt(i);
+ for (int j = approved.size() - 1; j >= 0; j--) {
+ final String approvedPackageOrComponent = approved.valueAt(j);
+ if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+ final ComponentName component = ComponentName.unflattenFromString(
+ approvedPackageOrComponent);
+ if (component != null && !componentHasBindPermission(component, userId)) {
+ approved.removeAt(j);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no bind permission found "
+ + mConfig.bindPermission);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
protected String getPackageName(String packageOrComponent) {
final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
if (component != null) {
@@ -1519,8 +1563,7 @@ abstract public class ManagedServices {
void reregisterService(final ComponentName cn, final int userId) {
// If rebinding a package that died, ensure it still has permission
// after the rebind delay
- if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
- || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
registerService(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index bc86c8216952..289faf45a253 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,8 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -536,36 +538,40 @@ public class ZenModeHelper {
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
requirePublicOrigin("updateAutomaticZenRule", origin);
- ZenModeConfig newConfig;
+ if (ruleId == null) {
+ throw new IllegalArgumentException("ruleId cannot be null");
+ }
synchronized (mConfigLock) {
if (mConfig == null) return false;
if (DEBUG) {
Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ " reason=" + reason);
}
- newConfig = mConfig.copy();
- ZenModeConfig.ZenRule rule;
- if (ruleId == null) {
- throw new IllegalArgumentException("Rule doesn't exist");
- } else {
- rule = newConfig.automaticRules.get(ruleId);
- if (rule == null || !canManageAutomaticZenRule(rule)) {
- throw new SecurityException(
- "Cannot update rules not owned by your condition provider");
- }
+ ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+ if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+ throw new SecurityException(
+ "Cannot update rules not owned by your condition provider");
}
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
if (!Flags.modesApi()) {
- if (rule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+ if (newRule.enabled != automaticZenRule.isEnabled()) {
+ dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
automaticZenRule.isEnabled()
? AUTOMATIC_RULE_STATUS_ENABLED
: AUTOMATIC_RULE_STATUS_DISABLED);
}
}
- populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+ boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+ origin, /* isNew= */ false);
+ if (Flags.modesApi() && !updated) {
+ // Bail out so we don't have the side effects of updating a rule (i.e. dropping
+ // condition) when no changes happen.
+ return true;
+ }
return setConfigLocked(newConfig, origin, reason,
- rule.component, true, callingUid);
+ newRule.component, true, callingUid);
}
}
@@ -1073,31 +1079,67 @@ public class ZenModeHelper {
return null;
}
- private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ /**
+ * Populates a {@code ZenRule} with the content of the {@link AutomaticZenRule}. Can be used for
+ * both rule creation or update (distinguished by the {@code isNew} parameter. The change is
+ * applied differently depending on the origin; for example app-provided changes might be
+ * ignored (if the rule was previously customized by the user), while user-provided changes
+ * update the user-modified bitmasks for any modifications.
+ *
+ * <p>Returns {@code true} if the rule was modified. Note that this is not equivalent to
+ * {@link ZenRule#equals} or {@link AutomaticZenRule#equals}, for various reasons:
+ * <ul>
+ * <li>some metadata-related fields are not considered
+ * <li>some fields (like {@code condition} are always reset, and ignored for this result
+ * <li>an app may provide changes that are not actually applied, as described above
+ * </ul>
+ */
+ private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
@ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
+ boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = mClock.millis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
+ modified = true;
}
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- if (rule.enabled != automaticZenRule.isEnabled()) {
+ if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+ rule.conditionId = azr.getConditionId();
+ modified = true;
+ }
+ if (rule.enabled != azr.isEnabled()) {
+ rule.enabled = azr.isEnabled();
rule.snoozing = false;
+ modified = true;
+ }
+ if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+ rule.configurationActivity = azr.getConfigurationActivity();
+ modified = true;
+ }
+ if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+ rule.allowManualInvocation = azr.isManualInvocationAllowed();
+ modified = true;
+ }
+ String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+ if (!Objects.equals(rule.iconResName, iconResName)) {
+ rule.iconResName = iconResName;
+ modified = true;
+ }
+ if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+ rule.triggerDescription = azr.getTriggerDescription();
+ modified = true;
+ }
+ if (rule.type != azr.getType()) {
+ rule.type = azr.getType();
+ modified = true;
}
- rule.enabled = automaticZenRule.isEnabled();
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
- rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
- rule.iconResName =
- drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
- rule.triggerDescription = automaticZenRule.getTriggerDescription();
- rule.type = automaticZenRule.getType();
// TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
- rule.modified = automaticZenRule.isModified();
+ rule.modified = azr.isModified();
// Name is treated differently than other values:
// App is allowed to update name if the name was not modified by the user (even if
@@ -1107,7 +1149,8 @@ public class ZenModeHelper {
String previousName = rule.name;
if (isNew || doesOriginAlwaysUpdateValues(origin)
|| (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
+ modified |= !Objects.equals(rule.name, previousName);
}
// For the remaining values, rules can always have all values updated if:
@@ -1119,50 +1162,56 @@ public class ZenModeHelper {
// For all other values, if updates are not allowed, we discard the update.
if (!updateValues) {
- return;
+ return modified;
}
// Updates the bitmasks if the origin of the change is the user.
boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
- if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+ if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
}
int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- if (updateBitmask && rule.zenMode != newZenMode) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ if (rule.zenMode != newZenMode) {
+ rule.zenMode = newZenMode;
+ if (updateBitmask) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+ modified = true;
}
- // Updates the values in the ZenRule itself.
- rule.zenMode = newZenMode;
-
// Updates the bitmask and values for all policy fields, based on the origin.
- updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+ modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
// Updates the bitmask and values for all device effect fields, based on the origin.
- updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
+ modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
+
+ return modified;
} else {
- if (rule.enabled != automaticZenRule.isEnabled()) {
+ if (rule.enabled != azr.isEnabled()) {
rule.snoozing = false;
}
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- rule.enabled = automaticZenRule.isEnabled();
- rule.modified = automaticZenRule.isModified();
- rule.zenPolicy = automaticZenRule.getZenPolicy();
+ rule.conditionId = azr.getConditionId();
+ rule.enabled = azr.isEnabled();
+ rule.modified = azr.isModified();
+ rule.zenPolicy = azr.getZenPolicy();
rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ rule.configurationActivity = azr.getConfigurationActivity();
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = System.currentTimeMillis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
}
+
+ // Only the MODES_API path cares about the result, so just return whatever here.
+ return true;
}
}
@@ -1182,16 +1231,19 @@ public class ZenModeHelper {
* provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
* for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
* reflect the changes being applied (if applicable, i.e. if the update is from the user).
+ *
+ * <p>Returns {@code true} if the policy of the rule was modified.
*/
- private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+ private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+ return true;
}
// Otherwise, a null policy means no policy changes, so we can stop here.
- return;
+ return false;
}
// If oldPolicy is null, we compare against the default policy when determining which
@@ -1272,6 +1324,8 @@ public class ZenModeHelper {
}
zenRule.zenPolicyUserModifiedFields = userModifiedFields;
}
+
+ return !newPolicy.equals(oldPolicy);
}
/**
@@ -1283,12 +1337,14 @@ public class ZenModeHelper {
* <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
* treated especially: for a new rule, they are blanked out; for an updated rule, previous
* values are preserved.
+ *
+ * <p>Returns {@code true} if the device effects of the rule were modified.
*/
- private static void updateZenDeviceEffects(ZenRule zenRule,
+ private static boolean updateZenDeviceEffects(ZenRule zenRule,
@Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
// Same as with ZenPolicy, supplying null effects means keeping the previous ones.
if (newEffects == null) {
- return;
+ return false;
}
ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
@@ -1349,6 +1405,8 @@ public class ZenModeHelper {
}
zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
+
+ return !newEffects.equals(oldEffects);
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -2505,7 +2563,7 @@ public class ZenModeHelper {
if (resId == 0) {
return null;
}
- Objects.requireNonNull(packageName);
+ requireNonNull(packageName);
try {
final Resources res = mPm.getResourcesForApplication(packageName);
return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 0abe50f12772..71800efae292 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -32,12 +32,13 @@ import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -47,11 +48,12 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
import android.util.Slog;
@@ -69,7 +71,7 @@ import java.util.Set;
* This is the system service for handling calls on the
* {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
* service holds connection references to the underlying remote services i.e. the isolated service
- * {@link OnDeviceTrustedInferenceService} and a regular
+ * {@link OnDeviceSandboxedInferenceService} and a regular
* service counter part {@link OnDeviceIntelligenceService}.
*
* Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
@@ -90,7 +92,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
protected final Object mLock = new Object();
- private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
@@ -165,7 +167,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(id, featureCallback));
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
@Override
@@ -185,7 +187,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(listFeaturesCallback));
+ service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
}
@Override
@@ -207,7 +209,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(feature, featureDetailsCallback));
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
@Override
@@ -227,33 +230,35 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(feature, cancellationSignal,
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
downloadCallback));
}
@Override
- public void requestTokenCount(Feature feature,
+ public void requestTokenInfo(Feature feature,
Content request, ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountcallback) throws RemoteException {
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
Objects.requireNonNull(feature);
Objects.requireNonNull(request);
- Objects.requireNonNull(tokenCountcallback);
+ Objects.requireNonNull(tokenInfoCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
- tokenCountcallback.onFailure(
+ tokenInfoCallback.onFailure(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.requestTokenCount(feature, request, cancellationSignal,
- tokenCountcallback));
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
+ cancellationSignal,
+ tokenInfoCallback));
}
@Override
@@ -267,7 +272,6 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
Objects.requireNonNull(feature);
Objects.requireNonNull(responseCallback);
- Objects.requireNonNull(request);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
@@ -277,9 +281,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequest(feature, request, requestType,
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
cancellationSignal, processingSignal,
responseCallback));
}
@@ -293,7 +298,6 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
IStreamingResponseCallback streamingCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(streamingCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
@@ -304,9 +308,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequestStreaming(feature, request, requestType,
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
cancellationSignal, processingSignal,
streamingCallback));
}
@@ -346,7 +351,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
Bundle processingState,
IProcessingUpdateStatusCallback callback) {
try {
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
service -> service.updateProcessingState(
processingState, callback));
@@ -363,22 +368,24 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
};
}
- private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+ private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceTrustedInferenceService);
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
validateService(serviceName, true);
- mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+ mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
mRemoteInferenceService.setServiceLifecycleCallbacks(
new ServiceConnector.ServiceLifecycleCallbacks<>() {
@Override
public void onConnected(
- @NonNull IOnDeviceTrustedInferenceService service) {
+ @NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
} catch (RemoteException ex) {
@@ -433,7 +440,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+ Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
if (!isIsolatedService(serviceInfo)) {
throw new SecurityException(
"Call required an isolated service, but the configured service: "
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index cc8e78804bb6..69ba1d2fb599 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,18 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import com.android.internal.infra.ServiceConnector;
/**
- * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
* logic set by the service implementation via a SecureSettings flag.
*/
-public class RemoteOnDeviceTrustedInferenceService extends
- ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+public class RemoteOnDeviceSandboxedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
/**
* Creates an instance of {@link ServiceConnector}
*
@@ -43,12 +43,12 @@ public class RemoteOnDeviceTrustedInferenceService extends
* {@link Context#unbindService unbinding}
* @param userId to be used for {@link Context#bindServiceAsUser binding}
*/
- RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+ RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
int userId) {
super(context, new Intent(
- OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
- IOnDeviceTrustedInferenceService.Stub::asInterface);
+ IOnDeviceSandboxedInferenceService.Stub::asInterface);
// Bind right away
connect();
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ef8453dcee67..29de26e41df4 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageManager.DELETE_ALL_USERS;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -754,8 +755,9 @@ public class PackageArchiver {
int draftSessionId;
try {
- draftSessionId = Binder.withCleanCallingIdentity(() ->
- createDraftSession(packageName, installerPackage, statusReceiver, userId));
+ draftSessionId = Binder.withCleanCallingIdentity(
+ () -> createDraftSession(packageName, installerPackage, callerPackageName,
+ statusReceiver, userId));
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +797,18 @@ public class PackageArchiver {
}
private int createDraftSession(String packageName, String installerPackage,
+ String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
- sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.setAppLabel(
+ mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+ sessionParams.setAppIcon(
+ getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+ // INSTALL_UNARCHIVE is also set.
+ sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 305b087190d6..5c8215e4bfdb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,10 @@
package com.android.server.pm.verify.domain;
+import static android.content.IntentFilter.WILDCARD;
+
+import static com.android.server.pm.verify.domain.DomainVerificationUtils.isValidDomain;
+
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -253,9 +257,18 @@ public class DomainVerificationService extends SystemService
Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
pkgState.getUriRelativeFilterGroupMap();
for (String domain : bundle.keySet()) {
+ if (!isValidDomain(domain)) {
+ continue;
+ }
ArrayList<UriRelativeFilterGroupParcel> parcels =
bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
- domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+ List<UriRelativeFilterGroup> groups =
+ UriRelativeFilterGroup.parcelsToGroups(parcels);
+ if (groups == null || groups.isEmpty()) {
+ domainToGroupsMap.remove(domain);
+ } else {
+ domainToGroupsMap.put(domain, groups);
+ }
}
}
}
@@ -273,9 +286,11 @@ public class DomainVerificationService extends SystemService
Map<String, List<UriRelativeFilterGroup>> map =
pkgState.getUriRelativeFilterGroupMap();
for (int i = 0; i < domains.size(); i++) {
- List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
- bundle.putParcelableList(domains.get(i),
- UriRelativeFilterGroup.groupsToParcels(groups));
+ if (map.containsKey(domains.get(i))) {
+ List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+ bundle.putParcelableList(domains.get(i),
+ UriRelativeFilterGroup.groupsToParcels(groups));
+ }
}
}
}
@@ -285,15 +300,29 @@ public class DomainVerificationService extends SystemService
@NonNull
private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
@NonNull String domain) {
- List<UriRelativeFilterGroup> groups = Collections.emptyList();
+ List<UriRelativeFilterGroup> groups;
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState != null) {
- groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
- Collections.emptyList());
+ Map<String, List<UriRelativeFilterGroup>> groupMap =
+ pkgState.getUriRelativeFilterGroupMap();
+ groups = groupMap.get(domain);
+ if (groups != null) {
+ return groups;
+ }
+ int first = domain.indexOf(".");
+ int second = domain.indexOf('.', first + 1);
+ while (first > 0 && second > 0) {
+ groups = groupMap.get(WILDCARD + domain.substring(first));
+ if (groups != null) {
+ return groups;
+ }
+ first = second;
+ second = domain.indexOf('.', second + 1);
+ }
}
}
- return groups;
+ return Collections.emptyList();
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 3fd00c6993cf..b8c4d22f186a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -35,6 +35,9 @@ import java.util.regex.Matcher;
public final class DomainVerificationUtils {
+ public static final int MAX_DOMAIN_LENGTH = 254;
+ public static final int MAX_DOMAIN_LABEL_LENGTH = 63;
+
private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
() -> Patterns.DOMAIN_NAME.matcher(""));
@@ -108,4 +111,41 @@ public final class DomainVerificationUtils {
appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
return appInfo;
}
+
+ static boolean isValidDomain(String domain) {
+ if (domain.length() > MAX_DOMAIN_LENGTH || domain.equals("*")) {
+ return false;
+ }
+ if (domain.charAt(0) == '*') {
+ if (domain.charAt(1) != '.') {
+ return false;
+ }
+ domain = domain.substring(2);
+ }
+ int labels = 1;
+ int labelStart = -1;
+ for (int i = 0; i < domain.length(); i++) {
+ char c = domain.charAt(i);
+ if (c == '.') {
+ int labelLength = i - labelStart - 1;
+ if (labelLength == 0 || labelLength > MAX_DOMAIN_LABEL_LENGTH) {
+ return false;
+ }
+ labelStart = i;
+ labels += 1;
+ } else if (!isValidDomainChar(c)) {
+ return false;
+ }
+ }
+ int lastLabelLength = domain.length() - labelStart - 1;
+ if (lastLabelLength == 0 || lastLabelLength > 63) {
+ return false;
+ }
+ return labels > 1;
+ }
+
+ private static boolean isValidDomainChar(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || c == '-';
+ }
}
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 24d7acd772c1..53607880ff48 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -700,6 +700,8 @@ public class ThermalManagerService extends SystemService {
return runOverrideStatus();
case "reset":
return runReset();
+ case "headroom":
+ return runHeadroom();
default:
return handleDefaultCommands(cmd);
}
@@ -862,6 +864,36 @@ public class ThermalManagerService extends SystemService {
}
}
+ private int runHeadroom() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final PrintWriter pw = getOutPrintWriter();
+ int forecastSecs;
+ try {
+ forecastSecs = Integer.parseInt(getNextArgRequired());
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex);
+ return -1;
+ }
+ if (!mHalReady.get()) {
+ pw.println("Error: thermal HAL is not ready");
+ return -1;
+ }
+
+ if (forecastSecs < MIN_FORECAST_SEC || forecastSecs > MAX_FORECAST_SEC) {
+ pw.println(
+ "Error: forecast second input should be in range [" + MIN_FORECAST_SEC
+ + "," + MAX_FORECAST_SEC + "]");
+ return -1;
+ }
+ float headroom = mTemperatureWatcher.getForecast(forecastSecs);
+ pw.println("Headroom in " + forecastSecs + " seconds: " + headroom);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -877,6 +909,9 @@ public class ThermalManagerService extends SystemService {
pw.println(" status code is defined in android.os.Temperature.");
pw.println(" reset");
pw.println(" unlocks the thermal status of the device.");
+ pw.println(" headroom FORECAST_SECONDS");
+ pw.println(" gets the thermal headroom forecast in specified seconds, from ["
+ + MIN_FORECAST_SEC + "," + MAX_FORECAST_SEC + "].");
pw.println();
}
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e5bd5e..79adcb438e6b 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,6 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
- userState.mIAppMap.clear();
userState.mAdServiceMap = adServiceMap;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 5b77433fa6d9..2fc183d9113f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -532,7 +532,7 @@ final class VibrationSettings {
return false;
}
- if (Flags.keyboardCategoryEnabled()) {
+ if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
int category = callerInfo.attrs.getCategory();
if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
// Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 9616c286f1b3..5175b74116f4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -131,19 +131,23 @@ public class WallpaperCropper {
(bitmapSize.y - crop.height()) / 2);
return crop;
}
+
+ // If any suggested crop is invalid, fallback to case 1
+ for (int i = 0; i < suggestedCrops.size(); i++) {
+ Rect testCrop = suggestedCrops.valueAt(i);
+ if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+ || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+ }
+
int orientation = getOrientation(displaySize);
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
- if (suggestedCrop.left < 0 || suggestedCrop.top < 0
- || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
- Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
- Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
- return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
- } else {
return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
- }
}
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -247,6 +251,7 @@ public class WallpaperCropper {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
+ if (cropRatio == screenRatio) return crop;
if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
@@ -276,6 +281,7 @@ public class WallpaperCropper {
}
}
} else {
+ // TODO (b/281648899) the third case is not always correct, fix that.
int widthToAdd = mode == REMOVE ? 0
: mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
: (int) (0.5 + crop.height() - crop.width());
@@ -646,6 +652,9 @@ public class WallpaperCropper {
if (!success) {
Slog.e(TAG, "Unable to apply new wallpaper");
wallpaper.getCropFile().delete();
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ wallpaper.mSampleSize = 1f;
}
if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672cd0a1..0165d65283dc 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@ public class WallpaperDataParser {
} else {
wallpaper.cropHint.set(totalCropHint);
}
+ wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
wallpaper.cropHint.set(totalCropHint);
}
@@ -493,6 +494,7 @@ public class WallpaperDataParser {
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
} else if (!multiCrop()) {
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d30a2167a183..7ba953dae55c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,7 +658,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean mVoiceInteraction;
- private int mPendingRelaunchCount;
+ int mPendingRelaunchCount;
long mRelaunchStartTime;
// True if we are current in the process of removing this app token from the display
@@ -3988,7 +3988,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// If the display does not have running activity, the configuration may need to be
// updated for restoring original orientation of the display.
if (next == null) {
- mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, mDisplayContent,
true /* deferResume */);
}
if (activityRemoved) {
@@ -6463,12 +6463,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* state to match that fact.
*/
void completeResumeLocked() {
- final boolean wasVisible = mVisibleRequested;
- setVisibility(true);
- if (!wasVisible) {
- // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
- mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
- }
idle = false;
results = null;
if (newIntents != null && newIntents.size() > 0) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6ad056f5a902..2c39c5875389 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1625,7 +1625,7 @@ class ActivityStarter {
final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
mRootWindowContainer.ensureVisibilityAndConfig(
- currentTop, currentTop.getDisplayId(), false /* deferResume */);
+ currentTop, currentTop.mDisplayContent, false /* deferResume */);
}
if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2cda1f55b038..826e332b5f3c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -842,7 +842,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused root task.
- mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(r, r.mDisplayContent,
true /* deferResume */);
}
@@ -1011,7 +1011,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (andResume && readyToResume()) {
// As part of the process of launching, ActivityThread also performs
// a resume.
- rootTask.minimalResumeActivityLocked(r);
+ r.setState(RESUMED, "realStartActivityLocked");
+ r.completeResumeLocked();
} else {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b88d7f7e50a8..83f44d23dbb1 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -520,37 +520,11 @@ class InsetsSourceProvider {
updateVisibility();
mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
mClientVisible, surfacePosition, getInsetsHint());
- mStateController.notifySurfaceTransactionReady(this, getSurfaceTransactionId(leash), true);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
- private long getSurfaceTransactionId(SurfaceControl leash) {
- // Here returns mNativeObject (long) as the ID instead of the leash itself so that
- // InsetsStateController won't keep referencing the leash unexpectedly.
- return leash != null ? leash.mNativeObject : 0;
- }
-
- /**
- * This is called when the surface transaction of the leash initialization has been committed.
- *
- * @param id Indicates which transaction is committed so that stale callbacks can be dropped.
- */
- void onSurfaceTransactionCommitted(long id) {
- if (mIsLeashReadyForDispatching) {
- return;
- }
- if (mControl == null) {
- return;
- }
- if (id != getSurfaceTransactionId(mControl.getLeash())) {
- return;
- }
- mIsLeashReadyForDispatching = true;
- mStateController.notifySurfaceTransactionReady(this, 0, false);
- }
-
void startSeamlessRotation() {
if (!mSeamlessRotating) {
mSeamlessRotating = true;
@@ -571,6 +545,10 @@ class InsetsSourceProvider {
return true;
}
+ void onSurfaceTransactionApplied() {
+ mIsLeashReadyForDispatching = true;
+ }
+
void setClientVisible(boolean clientVisible) {
if (mClientVisible == clientVisible) {
return;
@@ -755,7 +733,6 @@ class InsetsSourceProvider {
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mAdapter == this) {
mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
- mStateController.notifySurfaceTransactionReady(InsetsSourceProvider.this, 0, false);
mControl = null;
mControlTarget = null;
mAdapter = null;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ba578f642429..6b9fcf411ce1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -34,7 +34,6 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
-import android.util.SparseLongArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -59,7 +58,6 @@ class InsetsStateController {
private final DisplayContent mDisplayContent;
private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
- private final SparseLongArray mSurfaceTransactionIds = new SparseLongArray();
private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
mControlTargetProvidersMap = new ArrayMap<>();
private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -362,32 +360,14 @@ class InsetsStateController {
notifyPendingInsetsControlChanged();
}
- void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) {
- if (ready) {
- mSurfaceTransactionIds.put(provider.getSource().getId(), id);
- } else {
- mSurfaceTransactionIds.delete(provider.getSource().getId());
- }
- }
-
private void notifyPendingInsetsControlChanged() {
if (mPendingControlChanged.isEmpty()) {
return;
}
- final int size = mSurfaceTransactionIds.size();
- final SparseLongArray surfaceTransactionIds = new SparseLongArray(size);
- for (int i = 0; i < size; i++) {
- surfaceTransactionIds.append(
- mSurfaceTransactionIds.keyAt(i), mSurfaceTransactionIds.valueAt(i));
- }
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
- for (int i = 0; i < size; i++) {
- final int sourceId = surfaceTransactionIds.keyAt(i);
- final InsetsSourceProvider provider = mProviders.get(sourceId);
- if (provider == null) {
- continue;
- }
- provider.onSurfaceTransactionCommitted(surfaceTransactionIds.valueAt(i));
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
int displayId = mDisplayContent.getDisplayId();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07a03ebcc9b2..a75d3b6ef16d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1742,30 +1742,21 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
*
* @param starting The currently starting activity or {@code null} if there is
* none.
- * @param displayId The id of the display where operation is executed.
+ * @param displayContent The display where the operation is executed.
* @param deferResume Whether to defer resume while updating config.
- * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
- * because of configuration update.
*/
- boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
+ void ensureVisibilityAndConfig(@Nullable ActivityRecord starting,
+ @NonNull DisplayContent displayContent, boolean deferResume) {
// First ensure visibility without updating the config just yet. We need this to know what
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
- if (displayId == INVALID_DISPLAY) {
- // The caller didn't provide a valid display id, skip updating config.
- return true;
- }
-
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
- final DisplayContent displayContent = getDisplayContent(displayId);
- Configuration config = null;
- if (displayContent != null) {
- config = displayContent.updateOrientation(starting, true /* forceUpdate */);
- }
+ final Configuration config =
+ displayContent.updateOrientation(starting, true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
@@ -1773,13 +1764,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
starting.reportDescendantOrientationChangeIfNeeded();
}
- if (displayContent != null) {
- // Update the configuration of the activities on the display.
- return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
- deferResume);
- } else {
- return true;
- }
+ // Update the configuration of the activities on the display.
+ displayContent.updateDisplayOverrideConfigurationLocked(config, starting, deferResume);
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e16d869fdcd9..73aa3078d12b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -978,7 +978,6 @@ class Task extends TaskFragment {
}
effectiveUid = info.applicationInfo.uid;
mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
- stringName = null;
if (info.targetActivity == null) {
if (_intent != null) {
@@ -1045,6 +1044,7 @@ class Task extends TaskFragment {
updateTaskDescription();
}
mSupportsPictureInPicture = info.supportsPictureInPicture();
+ stringName = null;
// Re-adding the task to Recents once updated
if (inRecents) {
@@ -4948,13 +4948,6 @@ class Task extends TaskFragment {
}
}
- void minimalResumeActivityLocked(ActivityRecord r) {
- ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
- + "callers=%s", r, Debug.getCallers(5));
- r.setState(RESUMED, "minimalResumeActivityLocked");
- r.completeResumeLocked();
- }
-
void checkReadyForSleep() {
if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5863,7 +5856,7 @@ class Task extends TaskFragment {
}
mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
- mDisplayContent.mDisplayId, false /* deferResume */);
+ mDisplayContent, false /* deferResume */);
} finally {
if (mTransitionController.isShellTransitionsEnabled()) {
mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a818a721f964..597e901f62ef 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1536,10 +1536,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
next.setState(RESUMED, "resumeTopActivity");
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order.
- boolean notUpdated = true;
-
// Activity should also be visible if set mLaunchTaskBehind to true (see
// ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
if (shouldBeVisible(next)) {
@@ -1551,28 +1547,15 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// result of invisible window resize.
// TODO: Remove this once visibilities are set correctly immediately when
// starting an activity.
- notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ final int originalRelaunchingCount = next.mPendingRelaunchCount;
+ mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
false /* deferResume */);
- }
-
- if (notUpdated) {
- // The configuration update wasn't able to keep the existing
- // instance of the activity, and instead started a new one.
- // We should be all done, but let's just make sure our activity
- // is still at the top and schedule another run if something
- // weird happened.
- ActivityRecord nextNext = topRunningActivity();
- ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
- + "%s, new next: %s", next, nextNext);
- if (nextNext != next) {
- // Do over!
- mTaskSupervisor.scheduleResumeTopActivities();
- }
- if (!next.isVisibleRequested() || next.mAppStopped) {
- next.setVisibility(true);
+ if (next.mPendingRelaunchCount > originalRelaunchingCount) {
+ // The activity is scheduled to relaunch, then ResumeActivityItem will be also
+ // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
+ next.completeResumeLocked();
+ return true;
}
- next.completeResumeLocked();
- return true;
}
try {
@@ -1655,17 +1638,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
- // From this point on, if something goes wrong there is no way
- // to recover the activity.
- try {
- next.completeResumeLocked();
- } catch (Exception e) {
- // If any exception gets thrown, toss away this
- // activity and try the next one.
- Slog.w(TAG, "Exception thrown during resume of " + next, e);
- next.finishIfPossible("resume-exception", true /* oomAdj */);
- return true;
- }
+ next.completeResumeLocked();
} else {
// Whoops, need to restart this activity!
if (!next.hasBeenLaunched) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 7fc61e1ac7f3..a84a99a9fbc5 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -573,7 +573,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
// Capture the animation surface control for activity's main window
static class StartingWindowAnimationAdaptor implements AnimationAdapter {
- SurfaceControl mAnimationLeash;
+
@Override
public boolean getShowWallpaper() {
return false;
@@ -582,14 +582,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- mAnimationLeash = animationLeash;
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- if (mAnimationLeash == animationLeash) {
- mAnimationLeash = null;
- }
}
@Override
@@ -604,9 +600,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
- pw.print(mAnimationLeash);
- pw.println();
}
@Override
@@ -616,16 +609,16 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
static SurfaceControl applyStartingWindowAnimation(WindowState window) {
final SurfaceControl.Transaction t = window.getPendingTransaction();
- final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
- if (adaptor.mAnimationLeash == null) {
+ final SurfaceControl leash = window.getAnimationLeash();
+ if (leash == null) {
Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ " was removed");
return null;
}
- t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
- return adaptor.mAnimationLeash;
+ t.setPosition(leash, window.mSurfacePosition.x, window.mSurfacePosition.y);
+ return leash;
}
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
@@ -696,7 +689,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
removalInfo.roundedCornerRadius =
topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
+ removalInfo.mainFrame = new Rect(mainWindow.getFrame());
+ removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
+ mainWindow.mSurfacePosition.y);
}
}
try {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 5e7f1cbdd06e..b43a4540bbde 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -28,7 +28,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.content.Context;
-import android.os.HandlerExecutor;
import android.os.Trace;
import android.util.Slog;
import android.util.TimeUtils;
@@ -70,8 +69,6 @@ public class WindowAnimator {
private Choreographer mChoreographer;
- private final HandlerExecutor mExecutor;
-
/**
* Indicates whether we have an animation frame callback scheduled, which will happen at
* vsync-app and then schedule the animation tick at the right time (vsync-sf).
@@ -83,7 +80,8 @@ public class WindowAnimator {
* A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is
* executed and the corresponding transaction is closed and applied.
*/
- private ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+ private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+ private boolean mInExecuteAfterPrepareSurfacesRunnables;
private final SurfaceControl.Transaction mTransaction;
@@ -94,7 +92,6 @@ public class WindowAnimator {
mTransaction = service.mTransactionFactory.get();
service.mAnimationHandler.runWithScissors(
() -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
- mExecutor = new HandlerExecutor(service.mAnimationHandler);
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
@@ -200,19 +197,6 @@ public class WindowAnimator {
updateRunningExpensiveAnimationsLegacy();
}
- final ArrayList<Runnable> afterPrepareSurfacesRunnables = mAfterPrepareSurfacesRunnables;
- if (!afterPrepareSurfacesRunnables.isEmpty()) {
- mAfterPrepareSurfacesRunnables = new ArrayList<>();
- mTransaction.addTransactionCommittedListener(mExecutor, () -> {
- synchronized (mService.mGlobalLock) {
- // Traverse in order they were added.
- for (int i = 0, size = afterPrepareSurfacesRunnables.size(); i < size; i++) {
- afterPrepareSurfacesRunnables.get(i).run();
- }
- afterPrepareSurfacesRunnables.clear();
- }
- });
- }
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction");
mTransaction.apply();
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -220,6 +204,7 @@ public class WindowAnimator {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ executeAfterPrepareSurfacesRunnables();
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit"
@@ -301,10 +286,34 @@ public class WindowAnimator {
/**
* Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and
- * the corresponding transaction is closed, applied, and committed.
+ * the corresponding transaction is closed and applied.
*/
void addAfterPrepareSurfacesRunnable(Runnable r) {
+ // If runnables are already being handled in executeAfterPrepareSurfacesRunnable, then just
+ // immediately execute the runnable passed in.
+ if (mInExecuteAfterPrepareSurfacesRunnables) {
+ r.run();
+ return;
+ }
+
mAfterPrepareSurfacesRunnables.add(r);
scheduleAnimation();
}
+
+ void executeAfterPrepareSurfacesRunnables() {
+
+ // Don't even think about to start recursing!
+ if (mInExecuteAfterPrepareSurfacesRunnables) {
+ return;
+ }
+ mInExecuteAfterPrepareSurfacesRunnables = true;
+
+ // Traverse in order they were added.
+ final int size = mAfterPrepareSurfacesRunnables.size();
+ for (int i = 0; i < size; i++) {
+ mAfterPrepareSurfacesRunnables.get(i).run();
+ }
+ mAfterPrepareSurfacesRunnables.clear();
+ mInExecuteAfterPrepareSurfacesRunnables = false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1106a95fcdce..46bac161f0a6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -695,7 +695,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private boolean mDrawnStateEvaluated;
- private final Point mSurfacePosition = new Point();
+ /** The surface position relative to the parent container. */
+ final Point mSurfacePosition = new Point();
/**
* A region inside of this window to be excluded from touch.
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0b58543e076d..b32c54496002 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1112,6 +1112,8 @@ class PermissionService(private val service: AccessCheckingService) :
)
enforceCallingOrSelfAnyPermission(
"getAllPermissionStates",
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
Manifest.permission.GET_RUNTIME_PERMISSIONS
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 66e07175e7f5..c54a94eada0b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -83,11 +83,7 @@ class DomainVerificationManagerApiTest {
}
val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
- .isEmpty()
- assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
- .isEmpty()
+ assertThat(bundle.keySet()).isEmpty()
val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
pathGroup.addUriRelativeFilter(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3db9f9..440905137cf9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import org.junit.Before;
import org.junit.Test;
@@ -66,8 +65,6 @@ public class DisplayOffloadSessionImplTest {
mSession.stopOffload();
assertFalse(mSession.isActive());
- verify(mDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
// An inactive session shouldn't be stopped again
mSession.stopOffload();
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 e9315c8ed8e6..14d8a9c5f0f2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,7 @@ import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+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;
@@ -1559,6 +1561,43 @@ public final class DisplayPowerControllerTest {
verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+ }
+
+ @Test
+ public void testBrightness_AutomaticHigherPriorityThanOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+ // Now automatic brightness becomes available
+ brightness = 0.22f;
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(brightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b99ecf3978ae..14de527aa1f7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.PowerManager;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@ public class LocalDisplayAdapterTest {
verify(mDisplayOffloader).stopOffload();
assertFalse(mDisplayOffloadSession.isActive());
- verify(mMockedDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b86c7d..9b6cc0a4b377 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@ public final class DisplayBrightnessControllerTest {
OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
offloadBrightnessStrategy);
- mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+ assertTrue(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_OffloadStrategyNull() {
+ float brightness = 0.4f;
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ assertFalse(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_BrightnessUnchanged() {
+ float brightness = 0.4f;
+ OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+ when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+ offloadBrightnessStrategy);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+ assertFalse(brightnessUpdated);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 1c681ce21f02..0e89d8383a8f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -247,6 +247,7 @@ public final class DisplayBrightnessStrategySelectorTest {
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, Display.STATE_ON));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index ba462e363b4e..a5dc668944df 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,29 +188,6 @@ public class AutomaticBrightnessStrategyTest {
}
@Test
- public void testAutoBrightnessState_BrightnessReasonIsOffload() {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
- int targetDisplayState = Display.STATE_ON;
- boolean allowAutoBrightnessWhileDozing = false;
- int brightnessReason = BrightnessReason.REASON_OFFLOAD;
- int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- float lastUserSetBrightness = 0.2f;
- boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
- allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
- userSetBrightnessChanged);
- verify(mAutomaticBrightnessController)
- .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- mBrightnessConfiguration,
- lastUserSetBrightness,
- userSetBrightnessChanged, 0.5f,
- false, policy, true);
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
- }
-
- @Test
public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index fb47aa8b9983..9d3caa555e60 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -98,8 +98,6 @@ public abstract class BaseBroadcastQueueTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
-
@Mock
AppOpsService mAppOpsService;
@Mock
@@ -120,6 +118,7 @@ public abstract class BaseBroadcastQueueTest {
HandlerThread mHandlerThread;
TestLooperManager mLooper;
AtomicInteger mNextPid;
+ BroadcastHistory mEmptyHistory;
/**
* Map from PID to registered registered runtime receivers.
@@ -137,6 +136,13 @@ public abstract class BaseBroadcastQueueTest {
.acquireLooperManager(mHandlerThread.getLooper()));
mNextPid = new AtomicInteger(100);
+ mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ mEmptyHistory = new BroadcastHistory(mConstants) {
+ public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ // Ignored
+ }
+ };
+
LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -164,8 +170,6 @@ public abstract class BaseBroadcastQueueTest {
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
- mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
}
public void tearDown() throws Exception {
@@ -213,8 +217,8 @@ public abstract class BaseBroadcastQueueTest {
}
@Override
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
- return mBroadcastQueues;
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
+ return null;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index cc6fc80d9fa1..bcf297f37243 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -118,15 +118,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- mBroadcastQueues[0] = mImpl;
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mImpl);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e141faf599fa..56e5bd6b0937 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -85,12 +85,8 @@ import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.verification.VerificationMode;
@@ -100,7 +96,6 @@ import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -114,18 +109,10 @@ import java.util.function.UnaryOperator;
* Common tests for {@link BroadcastQueue} implementations.
*/
@MediumTest
-@RunWith(Parameterized.class)
@SuppressWarnings("GuardedBy")
public class BroadcastQueueTest extends BaseBroadcastQueueTest {
private static final String TAG = "BroadcastQueueTest";
- private final Impl mImpl;
-
- private enum Impl {
- DEFAULT,
- MODERN,
- }
-
private BroadcastQueue mQueue;
private UidObserver mUidObserver;
@@ -157,15 +144,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
- @Parameters(name = "impl={0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
- }
-
- public BroadcastQueueTest(Impl impl) {
- mImpl = impl;
- }
-
@Before
public void setUp() throws Exception {
super.setUp();
@@ -251,24 +229,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
- if (mImpl == Impl.DEFAULT) {
- mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
- mConstants, mSkipPolicy, emptyHistory, false,
- ProcessList.SCHED_GROUP_DEFAULT);
- } else if (mImpl == Impl.MODERN) {
- mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- } else {
- throw new UnsupportedOperationException();
- }
- mBroadcastQueues[0] = mQueue;
-
+ mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mQueue);
mQueue.start(mContext.getContentResolver());
// Set the constants after invoking BroadcastQueue.start() to ensure they don't
@@ -489,10 +452,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
private void assertHealth() {
- if (mImpl == Impl.MODERN) {
- // If this fails, it'll throw a clear reason message
- ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
- }
+ // If this fails, it'll throw a clear reason message
+ ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
}
private static Map<String, Object> asMap(Bundle bundle) {
@@ -814,18 +775,13 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
- if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
- // Nuance: the default implementation doesn't ask for manifest
- // cold-started apps to be thawed, but the modern stack does
- } else {
- // Confirm that app was thawed
- verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
-
- // Confirm that we added package to process
- verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
- anyLong(), any());
- }
+ // Confirm that app was thawed
+ verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
+
+ // Confirm that we added package to process
+ verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+ anyLong(), any());
// Confirm that we've reported package as being used
verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
@@ -868,9 +824,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
@Test
public void testWedged_Registered_Ordered() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -891,9 +844,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
@Test
public void testWedged_Registered_ResultTo() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -994,9 +944,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
getUidForPackage(PACKAGE_GREEN));
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
getUidForPackage(PACKAGE_BLUE));
verifyScheduleReceiver(receiverBlueApp, airplane);
@@ -1110,10 +1058,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
waitForIdle();
// Legacy stack does not remove registered receivers as part of
// cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), callerApp, USER_GUEST);
- verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
- }
+ verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+ verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
for (String pkg : new String[] {
PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
}) {
@@ -1199,9 +1145,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
@Test
@RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
public void testRepeatedKillWithoutNotify() throws Exception {
- // Legacy queue does not handle repeated kills that don't get notified.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1227,9 +1170,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
@@ -1273,11 +1214,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
assertNotEquals(receiverBlueApp, restartedReceiverBlueApp);
// Legacy queue will always try delivering the broadcast even if the process
// has been killed.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), receiverBlueApp, airplane);
- } else {
- verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
- }
+ verifyScheduleReceiver(never(), receiverBlueApp, airplane);
// Verify that the new process receives the broadcast.
verifyScheduleReceiver(times(1), restartedReceiverBlueApp, airplane);
}
@@ -1671,9 +1608,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
@Test
public void testPrioritized_withDeferrableBroadcasts() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1834,10 +1768,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
@Test
public void testReplacePending_withUrgentBroadcast() throws Exception {
- // The behavior is same with the legacy queue but AMS takes care of finding
- // the right queue and replacing the broadcast.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final Intent timeTickFirst = new Intent(Intent.ACTION_TIME_TICK);
@@ -1903,15 +1833,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
waitForIdle();
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
}
@Test
@@ -1931,14 +1855,10 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
withPriority(receiverGreenA, 5))));
waitForIdle();
- if (mImpl == Impl.DEFAULT) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- // In the modern queue, we don't end up replacing the old broadcast to
- // avoid creating priority inversion and so the process will receive
- // both the old and new broadcasts.
- verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
- }
+ // In the modern queue, we don't end up replacing the old broadcast to
+ // avoid creating priority inversion and so the process will receive
+ // both the old and new broadcasts.
+ verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
}
@Test
@@ -1966,11 +1886,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
}
@@ -2002,9 +1918,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
@Test
public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2224,16 +2137,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
waitForIdle();
- final int expectedTimes;
- switch (mImpl) {
- // Original stack requested for every single receiver; yikes
- case DEFAULT: expectedTimes = 64; break;
- // Modern stack requests once each time we promote a process to
- // running; we promote "green" twice, and "blue" and "yellow" once
- case MODERN: expectedTimes = 4; break;
- default: throw new UnsupportedOperationException();
- }
-
+ // Modern stack requests once each time we promote a process to
+ // running; we promote "green" twice, and "blue" and "yellow" once
+ final int expectedTimes = 4;
verify(mAms, times(expectedTimes))
.updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
}
@@ -2302,9 +2208,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
@Test
public void testDeferralPolicy_UntilActive() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2350,9 +2253,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
*/
@Test
public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN,
@@ -2384,9 +2284,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
@Test
public void testBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2418,9 +2315,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
@Test
public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f0efb795f5a1..878b945766af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -61,8 +61,6 @@ import android.util.SparseArray;
import androidx.test.filters.SmallTest;
-import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -747,68 +745,6 @@ public class BroadcastRecordTest {
}
}
- /**
- * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser}
- */
- @Test
- public void testDeferBootCompletedBroadcast_dispatcher() {
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true);
- }
-
- private void testDeferBootCompletedBroadcast_dispatcher_internal(String action,
- boolean isAllUidReady) {
- final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0});
- final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action));
- assertEquals(PACKAGE_LIST.length, br.receivers.size());
-
- SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked(
- mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL);
- // original BroadcastRecord receivers list is empty now.
- assertTrue(br.receivers.isEmpty());
- assertEquals(PACKAGE_LIST.length, deferred.size());
-
- DeferredBootCompletedBroadcastPerUser deferredPerUser =
- new DeferredBootCompletedBroadcastPerUser(USER0);
- deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred);
-
- if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size());
- } else if (action.equals(ACTION_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size());
- }
- }
-
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
String packageName, int userId) {
record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1226f0c0b315..872ac40a4d76 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -297,6 +297,10 @@ public class MockingOomAdjusterTests {
} else {
updateProcessRecordNodes(Arrays.asList(apps));
if (apps.length == 1) {
+ final ProcessRecord app = apps[0];
+ if (!sService.mProcessList.getLruProcessesLOSP().contains(app)) {
+ sService.mProcessList.getLruProcessesLOSP().add(app);
+ }
sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
@@ -475,7 +479,16 @@ public class MockingOomAdjusterTests {
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
+ final int expectedAdj;
+ if (sService.mConstants.ENABLE_NEW_OOMADJ) {
+ // A cached empty process can be at best a level higher than the min cached adj.
+ expectedAdj = sFirstCachedAdj;
+ } else {
+ // This is wrong but legacy behavior is going to be removed and not worth fixing.
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
SCHED_GROUP_BACKGROUND);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bcd778c234b..c6a6865f1cf1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -60,10 +60,13 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
@@ -71,12 +74,15 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.EmptyArray;
import android.util.SparseArray;
@@ -104,6 +110,9 @@ import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
public class FlexibilityControllerTest {
@@ -113,6 +122,9 @@ public class FlexibilityControllerTest {
private MockitoSession mMockingSession;
private BroadcastReceiver mBroadcastReceiver;
+ private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>();
+ private final SparseArray<TelephonyManager.CarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private JobStore mJobStore;
@@ -130,6 +142,10 @@ public class FlexibilityControllerTest {
@Mock
private PrefetchController mPrefetchController;
@Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
private PackageManager mPackageManager;
@Before
@@ -138,6 +154,7 @@ public class FlexibilityControllerTest {
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
+ .mockStatic(AppGlobals.class)
.mockStatic(LocalServices.class)
.startMocking();
// Called in StateController constructor.
@@ -167,17 +184,23 @@ public class FlexibilityControllerTest {
-> mDeviceConfigPropertiesBuilder.build())
.when(() -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
+ // Used in FlexibilityController.SpecialAppTracker.
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION))
+ .thenReturn(true);
//used to get jobs by UID
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
// Used in JobStatus.
- doReturn(mock(PackageManagerInternal.class))
- .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
// Freeze the clocks at a moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+ // Set empty set of privileged apps.
+ setSimSlotMappings(null);
+ setPowerWhitelistExceptIdle();
// Initialize real objects.
doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
@@ -249,9 +272,13 @@ public class FlexibilityControllerTest {
}
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+ return createJobStatus(testTag, job, SOURCE_PACKAGE);
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) {
JobInfo jobInfo = job.build();
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
+ jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
@@ -1084,7 +1111,6 @@ public class FlexibilityControllerTest {
@Test
public void testAllowlistedAppBypass() {
- setPowerWhitelistExceptIdle();
mFlexibilityController.onSystemServicesReady();
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
@@ -1118,6 +1144,148 @@ public class FlexibilityControllerTest {
}
@Test
+ public void testCarrierPrivilegedAppBypass() throws Exception {
+ mFlexibilityController.onSystemServicesReady();
+
+ final String carrier1Pkg1 = "com.test.carrier.1.pkg.1";
+ final String carrier1Pkg2 = "com.test.carrier.1.pkg.2";
+ final String carrier2Pkg = "com.test.carrier.2.pkg";
+ final String nonCarrierPkg = "com.test.normal.pkg";
+
+ setPackageUid(carrier1Pkg1, 1);
+ setPackageUid(carrier1Pkg2, 11);
+ setPackageUid(carrier2Pkg, 2);
+ setPackageUid(nonCarrierPkg, 3);
+
+ // Set the second carrier's privileged list before SIM configuration is sent to test
+ // initialization.
+ setCarrierPrivilegedAppList(2, carrier2Pkg);
+
+ UiccSlotMapping sim1 = mock(UiccSlotMapping.class);
+ UiccSlotMapping sim2 = mock(UiccSlotMapping.class);
+ doReturn(1).when(sim1).getLogicalSlotIndex();
+ doReturn(2).when(sim2).getLogicalSlotIndex();
+ setSimSlotMappings(List.of(sim1, sim2));
+
+ JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1);
+ JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1);
+ JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1);
+ JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1);
+ JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2);
+ JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2);
+ JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2);
+ JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2);
+ JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg);
+ JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg);
+ JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg);
+ JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg);
+ JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg);
+ JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg);
+ JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg);
+ JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg);
+
+ setCarrierPrivilegedAppList(1);
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Only mark the first package of carrier 1 as privileged. Only that app's jobs should
+ // be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Add the second package of carrier 1. Both apps' jobs should be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Remove a SIM slot. The relevant app's should no longer have exempted jobs.
+ setSimSlotMappings(List.of(sim1));
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+ }
+
+ @Test
public void testForegroundAppBypass() {
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
@@ -1753,6 +1921,24 @@ public class FlexibilityControllerTest {
}
}
+ private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) {
+ final ArraySet<String> packageSet = packages == null
+ ? new ArraySet<>() : new ArraySet<>(packages);
+ mCarrierPrivilegedApps.put(logicalIndex, packageSet);
+
+ TelephonyManager.CarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ if (callback != null) {
+ callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet());
+ waitForQuietModuleThread();
+ }
+ }
+
+ private void setPackageUid(final String pkgName, final int uid) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
private void setPowerWhitelistExceptIdle(String... packages) {
doReturn(packages == null ? EmptyArray.STRING : packages)
.when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
@@ -1763,6 +1949,47 @@ public class FlexibilityControllerTest {
}
}
+ private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) {
+ clearInvocations(mTelephonyManager);
+ final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null
+ ? Collections.emptyList() : simSlotMapping;
+ doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping();
+ if (mBroadcastReceiver != null) {
+ final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+ mBroadcastReceiver.onReceive(mContext, intent);
+ waitForQuietModuleThread();
+ }
+ if (returnedMapping.size() > 0) {
+ ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor =
+ ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class);
+ ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ final int minExpectedNewRegistrations = Math.max(0,
+ returnedMapping.size() - mCarrierPrivilegedCallbacks.size());
+ verify(mTelephonyManager, atLeast(minExpectedNewRegistrations))
+ .registerCarrierPrivilegesCallback(
+ logicalIndexCaptor.capture(), any(), callbackCaptor.capture());
+
+ final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues();
+ final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks =
+ callbackCaptor.getAllValues();
+ for (int i = 0; i < registeredIndices.size(); ++i) {
+ final int logicalIndex = registeredIndices.get(i);
+ final TelephonyManager.CarrierPrivilegesCallback callback =
+ registeredCallbacks.get(i);
+
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+
+ // The API contract promises a callback upon registration with the current list.
+ final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex);
+ callback.onCarrierPrivilegesChanged(
+ cpApps == null ? Collections.emptySet() : cpApps,
+ Collections.emptySet());
+ }
+ waitForQuietModuleThread();
+ }
+ }
+
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5e5181bdfeeb..0089d4cafaad 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,7 +109,6 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
new file mode 100644
index 000000000000..79766f8a46b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.appwidget
+
+import android.content.ComponentName
+import com.android.server.appwidget.AppWidgetServiceImpl.ApiCounter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ApiCounterTest {
+ private companion object {
+ const val RESET_INTERVAL_MS = 10L
+ const val MAX_CALLS_PER_INTERVAL = 2
+ }
+
+ private var currentTime = 0L
+
+ private val id =
+ AppWidgetServiceImpl.ProviderId(
+ /* uid= */ 123,
+ ComponentName("com.android.server.appwidget", "FakeProviderClass")
+ )
+ private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
+
+ @Test
+ fun tryApiCall() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 5L
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 11L
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+
+ @Test
+ fun remove() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ // remove should cause the call count to be 0 on the next tryApiCall
+ counter.remove(id)
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c8a5583de0b2..3aaac2e9cf1b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,7 +16,6 @@
package com.android.server.biometrics.sensors.face;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -235,26 +234,6 @@ public class FaceServiceTest {
}
@Test
- public void testAuthenticateInBackground() throws Exception {
- FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
- .build();
- initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
-
- mContext.getTestablePermissions().setPermission(
- USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
- mContext.getTestablePermissions().setPermission(
- USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
-
- final long operationId = 5;
- mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
- mFaceServiceReceiver, faceAuthenticateOptions);
-
- assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
- }
-
- @Test
public void testOptionsForDetect() throws Exception {
FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
.setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index 9ad2652e9c63..673140390ae7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -235,7 +235,7 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
}
@Test
- public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+ public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() {
enableAdjustOnlyAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
@@ -250,7 +250,22 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+ // We can't sent <Set Audio Volume Level> when using adjust-only AVB.
+ // Instead, we send <Give Audio Status>, to get the System Audio device's volume level.
+ // This ensures that we end up with a correct audio status in AudioService, even if it
+ // set it incorrectly because it assumed that we could send <Set Audio Volume Level>
+ assertThat(mNativeWrapper.getResultMessages().size()).isEqualTo(1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress())
+ );
+
+ // When we receive <Report Audio Status>, we notify AudioService of the volume level.
+ receiveReportAudioStatus(50,
+ true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(50 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+ anyInt());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 0aa72d00be1b..98e119cf0dad 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -184,13 +184,13 @@ public class HdmiCecMessageValidatorTest {
@Test
public void isValid_setMenuLanguage() {
- assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+ assertMessageValidity("0F:32:53:50:41").isEqualTo(OK);
assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
- assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
- assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
- assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
- assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+ assertMessageValidity("04:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("40:32").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("0F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+ assertMessageValidity("0F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
}
@Test
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b9ece9360980..e27bb4c8c3b6 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -40,19 +40,12 @@ import java.util.concurrent.Executor;
public class StubTransaction extends SurfaceControl.Transaction {
private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>();
- private HashSet<SurfaceControl.TransactionCommittedListener> mTransactionCommittedListeners =
- new HashSet<>();
@Override
public void apply() {
for (Runnable listener : mWindowInfosReportedListeners) {
listener.run();
}
- for (SurfaceControl.TransactionCommittedListener listener
- : mTransactionCommittedListeners) {
- listener.onTransactionCommitted();
- }
- mTransactionCommittedListeners.clear();
}
@Override
@@ -246,9 +239,6 @@ public class StubTransaction extends SurfaceControl.Transaction {
@Override
public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor,
SurfaceControl.TransactionCommittedListener listener) {
- SurfaceControl.TransactionCommittedListener listenerInner =
- () -> executor.execute(listener::onTransactionCommitted);
- mTransactionCommittedListeners.add(listenerInner);
return this;
}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10ec2f9..515898a883e8 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@ android_test {
"notification_flags_lib",
"platform-test-rules",
"SettingsLib",
+ "libprotobuf-java-lite",
+ "platformprotoslite",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d0342d..05b6c907069b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 78fb5705d5ac..bfc47fdef5cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -324,7 +324,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
.thenReturn(appPermissions);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+ IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+ if (UserManager.isHeadlessSystemUserMode()) {
+ currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+ }
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 000000000000..f724510eeb73
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+ @Test
+ public void testEnum_zenMode() {
+ testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+ }
+
+ @Test
+ public void testEnum_activeRuleType() {
+ testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyState() {
+ testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyChannelPolicy() {
+ testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+ }
+
+ @Test
+ public void testEnum_zenPolicyConversationType() {
+ testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+ }
+
+ @Test
+ public void testEnum_zenPolicyPeopleType() {
+ testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+ }
+
+ /**
+ * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+ * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+ * in the enum values defined in {@code loggingProtoEnumClass}.
+ *
+ * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+ * those, and the main goal of this test is that we don't forget to update the logging enum
+ * if new API enum values are added).
+ */
+ private static void testEnum(Class<?> apiClass, String apiPrefix,
+ Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+ String loggingPrefix) {
+ Map<String, Integer> apiConstants =
+ Arrays.stream(apiClass.getDeclaredFields())
+ .filter(f -> Modifier.isPublic(f.getModifiers()))
+ .filter(f -> Modifier.isStatic(f.getModifiers()))
+ .filter(f -> Modifier.isFinal(f.getModifiers()))
+ .filter(f -> f.getType().equals(int.class))
+ .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+ .collect(Collectors.toMap(
+ Field::getName,
+ ZenEnumTest::getStaticFieldIntValue));
+
+ Map<String, Integer> loggingConstants =
+ Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+ .collect(Collectors.toMap(
+ v -> v.toString(),
+ v -> v.getNumber()));
+
+ Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+ .collect(Collectors.toMap(
+ entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+ Map.Entry::getValue));
+
+ assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+ }
+
+ private static int getStaticFieldIntValue(Field f) {
+ try {
+ return f.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7c1adbc39033..c4d246052580 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4808,6 +4808,53 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
+ .setTriggerDescription("Whenever")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+ CONDITION_TRUE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
index 038f1db32d18..54ab367df381 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
@@ -135,7 +135,7 @@ public class GroupedAggregatedLogRecordsTest {
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_2, createTime++));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
@@ -155,7 +155,7 @@ public class GroupedAggregatedLogRecordsTest {
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f54c7e57828b..88a94830e98a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -604,7 +604,8 @@ public class VibrationSettingsTest {
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// Keyboard touch ignored.
assertVibrationIgnoredForAttributes(
@@ -628,7 +629,8 @@ public class VibrationSettingsTest {
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// General touch ignored.
assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -642,6 +644,25 @@ public class VibrationSettingsTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+ public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(false);
+
+ // General touch ignored.
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+ // Keyboard touch ignored.
+ assertVibrationIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build(),
+ Vibration.Status.IGNORED_FOR_SETTINGS);
+ }
+
+ @Test
public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
// Vibrations from the primary device is never ignored.
for (int usage : ALL_USAGES) {
@@ -953,6 +974,10 @@ public class VibrationSettingsTest {
when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
}
+ private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
+ when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+ }
+
private void deleteUserSetting(String settingName) {
Settings.System.putStringForUser(
mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 09e7b9141e04..45a2ba4bfcca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3147,12 +3147,12 @@ public class ActivityRecordTests extends WindowTestsBase {
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Although the activity is already visible, app
// transition animation should be applied on this activity. This might be unnecessary, but
// until we verify no logic relies on this behavior, we'll keep this as is.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(true);
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
@@ -3167,11 +3167,11 @@ public class ActivityRecordTests extends WindowTestsBase {
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be invisible. Since the visibility changes, app transition
// animation should be applied on this activity.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(false);
assertTrue(activity.isVisible());
assertFalse(activity.isVisibleRequested());
@@ -3187,7 +3187,6 @@ public class ActivityRecordTests extends WindowTestsBase {
// activity.
assertFalse(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Since the visibility changes, app transition
@@ -3389,6 +3388,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// frozen until the input started.
mDisplayContent.setImeLayeringTarget(app1);
mDisplayContent.updateImeInputAndControlTarget(app1);
+ mDisplayContent.computeImeTarget(true /* updateImeTarget */);
performSurfacePlacementAndWaitForWindowAnimator();
assertEquals(app1, mDisplayContent.getImeInputTarget());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 1a1fe95756d7..0c1fbf3cb3d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -94,7 +94,6 @@ public class AppTransitionControllerTest extends WindowTestsBase {
public void setUp() throws Exception {
assumeFalse(WindowManagerService.sEnableShellTransitions);
mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
- mWm.mAnimator.ready();
}
@Test
@@ -856,7 +855,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -887,7 +886,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity,
null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation is not run by the remote handler because the activity is filling the Task.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -922,7 +921,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity,
null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -947,7 +946,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -974,7 +973,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -998,7 +997,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation not run by the remote handler.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -1025,7 +1024,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation should not run by the remote handler when there are non-embedded activities of
// different UID.
@@ -1052,7 +1051,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation should not run by the remote handler when there is wallpaper in the transition.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -1086,7 +1085,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client and all activities are input disabled
// for untrusted animation.
@@ -1137,7 +1136,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client and all activities are input disabled
// for untrusted animation.
@@ -1179,7 +1178,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client, but input should not be dropped for
// fully trusted.
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 1f15ec3be3a8..2085d6140f68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -371,7 +371,6 @@ public class InsetsStateControllerTest extends WindowTestsBase {
mDisplayContent.getInsetsPolicy().updateBarControlTarget(app);
mDisplayContent.getInsetsPolicy().showTransient(statusBars(),
true /* isGestureOnSystemBar */);
- mWm.mAnimator.ready();
waitUntilWindowAnimatorIdle();
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index a1638019359b..11d9629cf25e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,6 +43,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -112,7 +113,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter,
mHandler, false /*isActivityEmbedding*/);
- mWm.mAnimator.ready();
}
private WindowState createAppOverlayWindow() {
@@ -136,7 +136,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -168,7 +168,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -290,7 +290,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -336,7 +336,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN,
false /* isVoiceInteraction */, null /* sources */);
mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
try {
@@ -363,7 +363,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -417,7 +417,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -471,7 +471,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -526,7 +526,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -559,7 +559,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -595,7 +595,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -645,7 +645,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -782,7 +782,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mDisplayContent.applySurfaceChangesTransaction();
mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
any(), any(), any(), any());
@@ -810,7 +810,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(transit);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
return adapter;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 527ea0d35f02..ce9050456681 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1239,7 +1239,7 @@ public class RootTaskTests extends WindowTestsBase {
final ActivityRecord activity1 = finishTopActivity(rootTask1);
assertEquals(DESTROYING, activity1.getState());
verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
- eq(display.mDisplayId), anyBoolean());
+ eq(display), anyBoolean());
}
private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index da11e6ad613a..649f5207e88f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -835,8 +835,6 @@ public class RootWindowContainerTests extends WindowTestsBase {
new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setSystemDecorations(true).build();
- doReturn(true).when(mRootWindowContainer)
- .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 7ab093d0ae13..a8f6fe86c823 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -546,7 +546,7 @@ public class SystemServicesTestRule implements TestRule {
// This makes sure all previous messages in the handler are fully processed vs. just popping
// them from the message queue.
final AtomicBoolean currentMessagesProcessed = new AtomicBoolean(false);
- wm.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
+ wm.mAnimator.getChoreographer().postFrameCallback(time -> {
synchronized (currentMessagesProcessed) {
currentMessagesProcessed.set(true);
currentMessagesProcessed.notifyAll();
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 8b44579eda33..36adeec74e36 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -380,6 +380,11 @@ public class UsbHostManager {
return false;
}
+ if (descriptors == null) {
+ Slog.e(TAG, "Failed to add device as the descriptor is null");
+ return false;
+ }
+
UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
&& !checkUsbInterfacesDenyListed(parser)) {
@@ -462,8 +467,7 @@ public class UsbHostManager {
}
// Tracking
- addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
- parser.getRawDescriptors());
+ addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, descriptors);
// Stats collection
FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index b84ff2977b34..12e04c24fa64 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -195,7 +195,8 @@ public class ImsService extends Service {
// whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
// not be set by users of ImsService.
CAPABILITY_SIP_DELEGATE_CREATION,
- CAPABILITY_TERMINAL_BASED_CALL_WAITING
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING,
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ImsServiceCapability {}
@@ -206,7 +207,9 @@ public class ImsService extends Service {
*/
private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
- CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
+ CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION",
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING, "TERMINAL_BASED_CALL_WAITING",
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");
/**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95ef64f..d658d5991a57 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@ java_library {
],
}
-android_library_import {
- name: "wm-flicker-window-extensions_nodeps",
- aars: ["libs/window-extensions-release.aar"],
+java_library {
+ name: "wm-flicker-window-extensions",
sdk_version: "current",
+ static_libs: [
+ "androidx.window.extensions_extensions-nodeps",
+ ],
+ installable: false,
}
java_library {
- name: "wm-flicker-window-extensions",
+ name: "wm-flicker-window-extensions-core",
sdk_version: "current",
static_libs: [
- "wm-flicker-window-extensions_nodeps",
+ "androidx.window.extensions.core_core-nodeps",
],
installable: false,
}
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514f4c89..000000000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff7263f..b1b314fcdb19 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
// ==========================================================
// Build the host executable: protoc-gen-javastream
// ==========================================================
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@ cc_defaults {
static_libs: ["libprotoc"],
}
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+ name: "java_streaming_proto_lib",
+ defaults: ["protoc-gen-stream-defaults"],
+ target: {
+ darwin: {
+ cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+ },
+ },
+ cflags: [
+ "-Wno-format-y2k",
+ "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+ ],
+
+ srcs: [
+ "java/java_proto_stream_code_generator.cpp",
+ ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -48,8 +75,13 @@ cc_binary_host {
],
defaults: ["protoc-gen-stream-defaults"],
+ static_libs: ["java_streaming_proto_lib"],
}
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-cppstream",
srcs: [
@@ -60,13 +92,31 @@ cc_binary_host {
}
// ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+ name: "StreamingProtoTest",
+ defaults: ["protoc-gen-stream-defaults"],
+ srcs: [
+ "test/unit/**/*.cpp",
+ ],
+ static_libs: [
+ "java_streaming_proto_lib",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// ==========================================================
// Build the java test
// ==========================================================
+
java_library {
- name: "StreamingProtoTest",
+ name: "StreamingProtoJavaIntegrationTest",
srcs: [
- "test/**/*.java",
- "test/**/*.proto",
+ "test/integration/**/*.java",
+ "test/integration/**/*.proto",
],
proto: {
type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 000000000000..9d61111fb5bd
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * 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.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string indent_more(const string& indent) {
+ return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i = 0; i < N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+ << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+ const string& indent) {
+ string optional_comment =
+ field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+ string repeated_comment =
+ field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed() ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+ const string& indent) {
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i = 0; i < N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums,
+ const vector<DescriptorProto>& messages) {
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+ // text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i = 0; i < N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i = 0; i < N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name =
+ file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ if (messages_to_compile.empty() || !enums.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+ messages.push_back(file_descriptor.message_type(i));
+
+ if (messages_to_compile.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+ }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+ enums, messages);
+ }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+ set<string>* messages_to_compile_out) {
+ string line;
+ while (getline(args_string_stream, line, ';')) {
+ stringstream line_ss(line);
+ string arg_name;
+ getline(line_ss, arg_name, ':');
+ if (arg_name == "include_filter") {
+ string full_message_name;
+ while (getline(line_ss, full_message_name, ',')) {
+ messages_to_compile_out->insert(full_message_name);
+ }
+ } else {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+ }
+ }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+ CodeGeneratorResponse response;
+
+ set<string> messages_to_compile;
+ auto request_params = request.parameter();
+ if (!request_params.empty()) {
+ parse_args_string(stringstream(request_params), &messages_to_compile);
+ }
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i = 0; i < N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor, messages_to_compile);
+ } else {
+ write_single_file(&response, file_descriptor, messages_to_compile);
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 000000000000..d2492f75d383
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H \ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a561a04..5b35504865f8 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
#include <stdio.h>
+
#include <iomanip>
#include <iostream>
-#include <sstream>
#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
- string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
- }
- return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
- if (file_descriptor.options().has_java_package()) {
- return file_descriptor.options().java_package();
- } else {
- return file_descriptor.package();
- }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
- string const package = make_java_package(file_descriptor);
- string result;
- if (package.size() > 0) {
- result = replace_string(package, '.', '/');
- result += '/';
- }
-
- result += class_name;
- result += ".java";
-
- return result;
-}
-
-static string
-indent_more(const string& indent)
-{
- return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
- const int N = enu.value_size();
- text << indent << "// enum " << enu.name() << endl;
- for (int i=0; i<N; i++) {
- const EnumValueDescriptorProto& value = enu.value(i);
- text << indent << "public static final int "
- << make_constant_name(value.name())
- << " = " << value.number() << ";" << endl;
- }
- text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
- string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
- ? "optional " : "";
- string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
- ? "repeated " : "";
- string proto_type = get_proto_type(field);
- string packed_comment = field.options().packed()
- ? " [packed=true]" : "";
- text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
- << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
- text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
- ios::fmtflags fmt(text.flags());
- text << setfill('0') << setw(16) << hex << get_field_id(field);
- text.flags(fmt);
-
- text << "L;" << endl;
-
- text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
- int N;
- const string indented = indent_more(indent);
-
- text << indent << "// message " << message.name() << endl;
- text << indent << "public final class " << message.name() << " {" << endl;
- text << endl;
-
- // Enums
- N = message.enum_type_size();
- for (int i=0; i<N; i++) {
- write_enum(text, message.enum_type(i), indented);
- }
-
- // Nested classes
- N = message.nested_type_size();
- for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented);
- }
-
- // Fields
- N = message.field_size();
- for (int i=0; i<N; i++) {
- write_field(text, message.field(i), indented);
- }
-
- text << indent << "}" << endl;
- text << endl;
-}
-
-/**
- * Write the contents of a file.
*
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
- const string& filename, bool generate_outer,
- const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
- stringstream text;
-
- string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
-
- text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
- text << "// source: " << file_descriptor.name() << endl << endl;
-
- if (package_name.size() > 0) {
- if (package_name.size() > 0) {
- text << "package " << package_name << ";" << endl;
- text << endl;
- }
- }
-
- // This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API
- text << "/** @hide */" << endl;
-// text << "@android.annotation.TestApi" << endl;
-
- if (generate_outer) {
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
- }
-
- size_t N;
- const string indented = generate_outer ? indent_more("") : string();
-
- N = enums.size();
- for (size_t i=0; i<N; i++) {
- write_enum(text, enums[i], indented);
- }
-
- N = messages.size();
- for (size_t i=0; i<N; i++) {
- write_message(text, messages[i], indented);
- }
-
- if (generate_outer) {
- text << "}" << endl;
- }
-
- CodeGeneratorResponse::File* file_response = response->add_file();
- file_response->set_name(filename);
- file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class. Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- // If there is anything to put in the outer class file, create one
- if (file_descriptor.enum_type_size() > 0) {
- vector<EnumDescriptorProto> enums;
- int N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
- }
-
- // For each of the message types, make a file
- int N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- vector<EnumDescriptorProto> enums;
-
- vector<DescriptorProto> messages;
- messages.push_back(file_descriptor.message_type(i));
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
- false, enums, messages);
- }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- int N;
-
- vector<EnumDescriptorProto> enums;
- N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
- N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- messages.push_back(file_descriptor.message_type(i));
- }
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
-}
-
-/**
* Main.
*/
int
@@ -273,24 +26,11 @@ main(int argc, char const*const* argv)
GOOGLE_PROTOBUF_VERIFY_VERSION;
- CodeGeneratorRequest request;
- CodeGeneratorResponse response;
-
// Read the request
+ CodeGeneratorRequest request;
request.ParseFromIstream(&cin);
- // Build the files we need.
- const int N = request.proto_file_size();
- for (int i=0; i<N; i++) {
- const FileDescriptorProto& file_descriptor = request.proto_file(i);
- if (should_generate_for_file(request, file_descriptor.name())) {
- if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor);
- } else {
- write_single_file(&response, file_descriptor);
- }
- }
- }
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
// If we had errors, don't write the response. Print the errors and exit.
if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
index 05c8f0c54fac..05c8f0c54fac 100644
--- a/tools/streaming_proto/test/imported.proto
+++ b/tools/streaming_proto/test/integration/imported.proto
diff --git a/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 000000000000..2a7001b294ea
--- /dev/null
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,23 @@
+/*
+ * 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.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
index de80ed6612fc..3cf81b4ffd56 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
syntax = "proto2";
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
package com.android.streaming_proto_test;
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f539b44b..000000000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
- public void main(String[] argv) {
- System.out.println("hello world");
- }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 000000000000..8df9716b312d
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyOtherTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyOtherTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+ CodeGeneratorRequest request;
+
+ add_my_test_proto_file(&request);
+ add_my_other_test_proto_file(&request);
+
+ return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+ CodeGeneratorRequest request;
+
+ request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+ FileDescriptorProto* file_desc = request.add_proto_file();
+ file_desc->set_name("MyMultiMessageTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(true);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+
+ message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+
+ return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+ EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}