summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp14
-rw-r--r--api/Android.bp1
-rw-r--r--api/ApiDocs.bp1
-rw-r--r--boot/Android.bp4
-rw-r--r--core/api/current.txt7
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/java/android/app/ActivityManager.java48
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl2
-rw-r--r--core/java/android/app/Notification.java34
-rw-r--r--core/java/android/app/UiModeManager.java59
-rw-r--r--core/java/android/app/ui_mode_manager.aconfig8
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java11
-rw-r--r--core/java/android/app/wearable/flags.aconfig8
-rw-r--r--core/java/android/content/AttributionSource.java5
-rw-r--r--core/java/android/content/pm/flags.aconfig16
-rw-r--r--core/java/android/content/pm/multiuser.aconfig14
-rw-r--r--core/java/android/content/pm/overlay/OverlayPaths.java7
-rw-r--r--core/java/android/content/res/ResourcesImpl.java21
-rw-r--r--core/java/android/credentials/flags.aconfig9
-rw-r--r--core/java/android/ddm/DdmHandleViewDebug.java218
-rw-r--r--core/java/android/hardware/biometrics/AuthenticationStateListener.aidl13
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--core/java/android/os/VibrationAttributes.java2
-rw-r--r--core/java/android/provider/ContactKeysManager.java117
-rw-r--r--core/java/android/view/FocusFinder.java21
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/ScreenRecordingCallbacks.java146
-rw-r--r--core/java/android/view/View.java93
-rw-r--r--core/java/android/view/ViewDebug.java279
-rw-r--r--core/java/android/view/ViewRootImpl.java19
-rw-r--r--core/java/android/view/WindowManager.java63
-rw-r--r--core/java/android/view/WindowManagerImpl.java23
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java2
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig24
-rw-r--r--core/java/android/webkit/URLUtil.java4
-rw-r--r--core/java/android/widget/flags/notification_widget_flags.aconfig8
-rw-r--r--core/java/com/android/internal/widget/EmphasizedNotificationButton.java380
-rw-r--r--core/java/com/android/internal/widget/NotificationActionListLayout.java88
-rw-r--r--core/res/AndroidManifest.xml30
-rw-r--r--core/res/res/drawable/ic_satellite_alt_24px.xml25
-rw-r--r--core/res/res/values/strings.xml41
-rw-r--r--core/res/res/values/symbols.xml17
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/src/android/app/ActivityManagerTest.java83
-rw-r--r--core/tests/coretests/src/android/ddm/OWNERS1
-rw-r--r--core/tests/coretests/src/android/os/HandlerThreadTest.java31
-rw-r--r--core/tests/coretests/src/android/os/VibrationAttributesTest.java38
-rw-r--r--core/tests/coretests/src/android/view/ViewDebugTest.java (renamed from core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java)10
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java161
-rw-r--r--core/tests/utiltests/Android.bp1
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp92
-rw-r--r--location/java/android/location/altitude/AltitudeConverter.java21
-rw-r--r--media/java/android/media/BluetoothProfileConnectionInfo.java12
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig7
-rw-r--r--nfc/api/current.txt3
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java38
-rw-r--r--packages/CredentialManager/res/drawable/more_horiz_24px.xml1
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml2
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml1
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt2
-rw-r--r--packages/PackageInstaller/res/values/strings.xml2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java24
-rw-r--r--packages/SettingsLib/res/values/strings.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt161
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt45
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt38
-rw-r--r--packages/SettingsLib/tests/integ/Android.bp13
-rw-r--r--packages/SettingsLib/tests/integ/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/tests/integ/AndroidTest.xml1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt281
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt58
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt5
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java3
-rw-r--r--packages/SettingsLib/tests/unit/Android.bp2
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig31
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt319
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt38
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt8
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt45
-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/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt142
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java (renamed from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt (renamed from packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt)36
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt156
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt121
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt74
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt27
-rw-r--r--ravenwood/README-ravenwood+mockito.md24
-rw-r--r--ravenwood/bulk_enable.py6
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java47
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java8
-rwxr-xr-xravenwood/list-ravenwood-tests.sh18
-rw-r--r--ravenwood/mockito/Android.bp12
-rw-r--r--ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java10
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt3
-rwxr-xr-xravenwood/run-ravenwood-tests.sh22
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java4
-rw-r--r--services/autofill/features.aconfig9
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java91
-rw-r--r--services/core/java/com/android/server/am/AssistDataRequester.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java27
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java19
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java148
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettings.java7
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java4
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java1
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/om/IdmapManager.java2
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java115
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java14
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java18
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java11
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/core/java/com/android/server/wm/PerfettoTransitionTracer.java38
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java22
-rw-r--r--services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java64
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TrustedOverlayHost.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java47
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java28
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java6
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java17
323 files changed, 6925 insertions, 1873 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ee7ace5f661..e6e835b05295 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@ aconfig_srcjars = [
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
@@ -1123,3 +1124,16 @@ java_aconfig_library {
],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Wearable Sensing
+aconfig_declarations {
+ name: "android.app.wearable.flags-aconfig",
+ package: "android.app.wearable",
+ srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.wearable.flags-aconfig-java",
+ aconfig_declarations: "android.app.wearable.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/Android.bp b/api/Android.bp
index b3b18b66e097..ef64a89a892d 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -115,6 +115,7 @@ combined_apis {
"framework-pdf",
"framework-permission",
"framework-permission-s",
+ "framework-profiling",
"framework-scheduling",
"framework-sdkextensions",
"framework-statsd",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7ae3224e7500..7ee43191a80a 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -67,6 +67,7 @@ stubs_defaults {
":framework-ondevicepersonalization-sources",
":framework-permission-sources",
":framework-permission-s-sources",
+ ":framework-profiling-sources",
":framework-scheduling-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
diff --git a/boot/Android.bp b/boot/Android.bp
index 228d060bf9cf..cdfa7c80bc93 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -122,6 +122,10 @@ custom_platform_bootclasspath {
module: "com.android.permission-bootclasspath-fragment",
},
{
+ apex: "com.android.profiling",
+ module: "com.android.profiling-bootclasspath-fragment",
+ },
+ {
apex: "com.android.scheduling",
module: "com.android.scheduling-bootclasspath-fragment",
},
diff --git a/core/api/current.txt b/core/api/current.txt
index cf5a261f583d..b17e3343666e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@ package android {
field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+ field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@ package android {
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
field public static final String DUMP = "android.permission.DUMP";
@@ -44544,6 +44546,7 @@ package android.telephony {
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
method @Nullable public String getRegisteredPlmn();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_registration_info_reject_cause") public int getRejectCause();
method public int getTransportType();
method public boolean isNetworkRegistered();
method public boolean isNetworkRoaming();
@@ -53782,6 +53785,7 @@ package android.view {
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53791,6 +53795,7 @@ package android.view {
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+ method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
method public void removeViewImmediate(android.view.View);
method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53810,6 +53815,8 @@ package android.view {
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 55ed1f559f51..d33145529f2a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -187,6 +187,7 @@ package android.media {
method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
+ method @FlaggedApi("android.media.audio.sco_managed_by_audio") @NonNull public static android.media.BluetoothProfileConnectionInfo createHfpInfo();
method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
method public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 86f2b6281254..e6040f8cf68b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -414,6 +414,7 @@ package android {
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+ field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
}
public static final class Manifest.permission_group {
@@ -3157,6 +3158,7 @@ package android.app.wearable {
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
}
@@ -11303,6 +11305,12 @@ package android.provider {
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ }
+
@Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -14128,7 +14136,6 @@ package android.telephony {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getNetworkRegistrationState();
method @Deprecated public int getRegistrationState();
- method public int getRejectCause();
method public int getRoamingType();
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6285eb3b2096..084c71f47603 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -147,6 +147,7 @@ import java.util.function.Consumer;
* </p>
*/
@SystemService(Context.ACTIVITY_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class ActivityManager {
private static String TAG = "ActivityManager";
@@ -966,6 +967,7 @@ public class ActivityManager {
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@ public class ActivityManager {
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@ public class ActivityManager {
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
printCapabilitiesSummary(pw, caps);
final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@ public class ActivityManager {
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String getCapabilitiesSummary(@ProcessCapability int caps) {
final StringBuilder sb = new StringBuilder();
printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@ public class ActivityManager {
* @return the value of the corresponding enums.proto ProcessStateEnum value.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final int processStateAmToProto(int amInt) {
switch (amInt) {
case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@ public class ActivityManager {
public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
/** @hide Should this process state be considered a background state? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateBackground(int procState) {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
/** @hide Should this process state be considered in the cache? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateCached(int procState) {
return procState >= PROCESS_STATE_CACHED_ACTIVITY;
}
/** @hide Is this a foreground service type? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
}
@@ -1161,10 +1170,25 @@ public class ActivityManager {
mContext = context;
}
+ private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood(int currentUser) {
+ sCurrentUser$ravenwood = currentUser;
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ sCurrentUser$ravenwood = UserHandle.USER_NULL;
+ }
+
/**
* Returns whether the launch was successful.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultSuccessful(int result) {
return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
}
@@ -1173,6 +1197,7 @@ public class ActivityManager {
* Returns whether the launch result was a fatal error.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultFatalError(int result) {
return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
}
@@ -1343,6 +1368,7 @@ public class ActivityManager {
public @interface RestrictionLevel{}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
switch (level) {
case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@ public class ActivityManager {
* Returns "true" if the user interface is currently being messed with
* by a monkey.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isUserAMonkey() {
try {
return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@ public class ActivityManager {
}
}
+ /** @hide */
+ public static boolean isUserAMonkey$ravenwood() {
+ // Ravenwood environment is never considered a "monkey"
+ return false;
+ }
+
/**
* Returns "true" if device is running in a test harness.
*
@@ -4973,6 +5006,7 @@ public class ActivityManager {
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.INTERACT_ACROSS_USERS_FULL"
})
+ @android.ravenwood.annotation.RavenwoodReplace
public static int getCurrentUser() {
try {
return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@ public class ActivityManager {
}
}
+ /** @hide */
+ public static int getCurrentUser$ravenwood() {
+ return sCurrentUser$ravenwood;
+ }
+
/**
* @param userid the user's id. Zero indicates the default user.
* @hide
@@ -5320,6 +5359,7 @@ public class ActivityManager {
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isSystemReady() {
if (!sSystemReady) {
if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@ public class ActivityManager {
return sSystemReady;
}
+ /** @hide */
+ public static boolean isSystemReady$ravenwood() {
+ // Ravenwood environment is always considered as booted and ready
+ return true;
+ }
+
/**
* @hide
*/
@@ -5661,11 +5707,13 @@ public class ActivityManager {
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String procStateToString(int procState) {
final String procStateStr;
switch (procState) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5d2a26e13ee7..4c54b03bd6d6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -303,7 +303,7 @@ public final class ActivityThread extends ClientTransactionHandler
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
public static final boolean DEBUG_ORDER = false;
- private static final boolean DEBUG_APP_INFO = true;
+ private static final boolean DEBUG_APP_INFO = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
* The delay to release the provider when it has no more references. It reduces the number of
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 79070595e831..0dbce97c978b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1550,7 +1550,7 @@ public class AppOpsManager {
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
- * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ * Allows an app with a major use case of backing-up or syncing content to run longer jobs.
*
* @hide
*/
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d54074818b41..e2e2f1d3401e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@ interface IActivityTaskManager {
boolean focused, boolean newSessionId);
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
- boolean isAssistDataAllowedOnCurrentActivity();
+ boolean isAssistDataAllowed();
boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
in String callingPackageName, String callingAttributionTag);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb706e8..88839071bf6c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5946,6 +5946,12 @@ public class Notification implements Parcelable
// there is enough space to do so (and fall back to the left edge if not).
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "setting evenly divided mode on action list");
+ }
+ big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+ }
}
big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@ public class Notification implements Parcelable
// Remove full-length color spans and ensure text contrast with the button fill.
title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
}
- button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+ final CharSequence label = ensureColorSpanContrast(title, p);
+ if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+ }
+ button.setCharSequence(R.id.action0, "glueLabel", label);
+ } else {
+ button.setTextViewText(R.id.action0, label);
+ }
int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
if (tombstone) {
@@ -6438,7 +6452,14 @@ public class Notification implements Parcelable
button.setColorStateList(R.id.action0, "setButtonBackground",
ColorStateList.valueOf(buttonFillColor));
if (p.mCallStyleActions) {
- button.setImageViewIcon(R.id.action0, action.getIcon());
+ if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+ if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+ }
+ button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+ } else {
+ button.setImageViewIcon(R.id.action0, action.getIcon());
+ }
boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
button.setBoolean(R.id.action0, "setIsPriority", priority);
int minWidthDimen =
@@ -9565,6 +9586,15 @@ public class Notification implements Parcelable
* </pre>
*/
public static class CallStyle extends Style {
+ /**
+ * @hide
+ */
+ public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+ /**
+ * @hide
+ */
+ public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
/**
* @hide
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a27132872521..a045eae9e108 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.Flags.enableNightModeCache;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -31,6 +33,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.IpcDataCache;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@ public class UiModeManager {
}
}
+ private Integer getNightModeFromServer() {
+ try {
+ if (sGlobals != null) {
+ return sGlobals.mService.getNightMode();
+ }
+ return -1;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Retrieve the night mode for the user.
+ */
+ private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+ new IpcDataCache.QueryHandler<>() {
+
+ @Override
+ @NonNull
+ public Integer apply(Void query) {
+ return getNightModeFromServer();
+ }
+ };
+
+ private static final String NIGHT_MODE_API = "getNightMode";
+
+ /**
+ * Cache the night mode for a user.
+ */
+ private final IpcDataCache<Void, Integer> mNightModeCache =
+ new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+ /**
+ * Invalidate the night mode cache.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ public static void invalidateNightModeCache() {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NIGHT_MODE_API);
+ }
+
/**
* Returns the currently configured night mode.
* <p>
@@ -890,14 +938,11 @@ public class UiModeManager {
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (sGlobals != null) {
- try {
- return sGlobals.mService.getNightMode();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (enableNightModeCache()) {
+ return mNightModeCache.query(null);
+ } else {
+ return getNightModeFromServer();
}
- return -1;
}
/**
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 000000000000..1ae5264a7e8e
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ namespace: "system_performance"
+ name: "enable_night_mode_cache"
+ description: "Enables the use of binder caching for system night mode."
+ bug: "255999432"
+} \ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca0864a6dd..eca0039c20f4 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@ package android.app.wearable;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -99,6 +100,13 @@ public class WearableSensingManager {
*/
public static final int STATUS_ACCESS_DENIED = 5;
+ /**
+ * The value of the status code that indicates the method called is not supported by the
+ * implementation of {@link WearableSensingService}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+ public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -106,7 +114,8 @@ public class WearableSensingManager {
STATUS_UNSUPPORTED,
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
- STATUS_ACCESS_DENIED
+ STATUS_ACCESS_DENIED,
+ STATUS_UNSUPPORTED_OPERATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 000000000000..074ce9bb0e82
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.wearable"
+
+flag {
+ name: "enable_unsupported_operation_status_code"
+ namespace: "machine_learning"
+ description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+ bug: "301427767"
+} \ No newline at end of file
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bde562dbf95b..af1301140358 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -409,9 +409,10 @@ public final class AttributionSource implements Parcelable {
"packageName = " + mAttributionSourceState.packageName + ", " +
"attributionTag = " + mAttributionSourceState.attributionTag + ", " +
"token = " + mAttributionSourceState.token + ", " +
+ "deviceId = " + mAttributionSourceState.deviceId + ", " +
"next = " + (mAttributionSourceState.next != null
- && mAttributionSourceState.next.length > 0
- ? mAttributionSourceState.next[0] : null) +
+ && mAttributionSourceState.next.length > 0
+ ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
" }";
}
return super.toString();
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index caff4576c9c3..19bce0bb3abd 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -183,3 +183,19 @@ flag {
description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
bug: "321080601"
}
+
+flag {
+ name: "asl_in_apk_app_metadata_source"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer."
+ bug: "287487923"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "force_multi_arch_native_libs_match"
+ namespace: "package_manager_service"
+ description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+ bug: "282783453"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607f75f7..d7e64b66763e 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,17 @@ flag {
bug: "316362775"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_permission_to_access_hidden_profiles"
+ namespace: "profile_experiences"
+ description: "Add permission to access API hidden users data via system APIs"
+ bug: "321988638"
+}
+
+flag {
+ name: "handle_interleaved_settings_for_private_space"
+ namespace: "profile_experiences"
+ description: "Handle listing of private space apps in settings pages with interleaved content"
+ bug: "323212460"
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index bd74b0b9293c..a4db733af013 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,13 +49,6 @@ public class OverlayPaths {
public static class Builder {
final OverlayPaths mPaths = new OverlayPaths();
- public Builder() {}
-
- public Builder(@NonNull OverlayPaths base) {
- mPaths.mResourceDirs.addAll(base.getResourceDirs());
- mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
- }
-
/**
* Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
*/
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c7790bd96c62..5e442b819774 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -273,14 +273,27 @@ public class ResourcesImpl {
throw new NotFoundException("String resource name " + name);
}
+ private static boolean isIntLike(@NonNull String s) {
+ if (s.isEmpty() || s.length() > 10) return false;
+ for (int i = 0, size = s.length(); i < size; i++) {
+ final char c = s.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getIdentifier(String name, String defType, String defPackage) {
if (name == null) {
throw new NullPointerException("name is null");
}
- try {
- return Integer.parseInt(name);
- } catch (Exception e) {
- // Ignore
+ if (isIntLike(name)) {
+ try {
+ return Integer.parseInt(name);
+ } catch (Exception e) {
+ // Ignore
+ }
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 90cd4718fcee..1ca11e698d5d 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -47,4 +47,11 @@ flag {
name: "configurable_selector_ui_enabled"
description: "Enables OEM configurable Credential Selector UI"
bug: "319448437"
-} \ No newline at end of file
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "credman_biometric_api_enabled"
+ description: "Enables Credential Manager to work with the Biometric Authenticate API"
+ bug: "323211850"
+}
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 0f66fcbdbec9..5cbf24f3ba54 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,16 +16,12 @@
package android.ddm;
-import static com.android.internal.util.Preconditions.checkArgument;
-
import android.util.Log;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
-import com.android.internal.annotations.VisibleForTesting;
-
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -35,10 +31,8 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
/**
* Handle various requests related to profiling / debugging of the view system.
@@ -352,48 +346,17 @@ public class DdmHandleViewDebug extends DdmHandle {
*
* The return value is encoded the same way as a single parameter (type + value)
*/
- private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+ private Chunk invokeViewMethod(View rootView, final View targetView, ByteBuffer in) {
int l = in.getInt();
String methodName = getString(in, l);
- Class<?>[] argTypes;
- Object[] args;
- if (!in.hasRemaining()) {
- argTypes = new Class<?>[0];
- args = new Object[0];
- } else {
- int nArgs = in.getInt();
- argTypes = new Class<?>[nArgs];
- args = new Object[nArgs];
-
- try {
- deserializeMethodParameters(args, argTypes, in);
- } catch (ViewMethodInvocationSerializationException e) {
- return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
- }
- }
-
- Method method;
- try {
- method = targetView.getClass().getMethod(methodName, argTypes);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "No such method: " + e.getMessage());
- return createFailChunk(ERR_INVALID_PARAM,
- "No such method: " + e.getMessage());
- }
-
try {
- Object result = ViewDebug.invokeViewMethod(targetView, method, args);
- Class<?> returnType = method.getReturnType();
- byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+ byte[] returnValue = ViewDebug.invokeViewMethod(targetView, methodName, in);
return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
+ } catch (ViewDebug.ViewMethodInvocationSerializationException e) {
+ return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
} catch (Exception e) {
- Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
- String msg = e.getCause().getMessage();
- if (msg == null) {
- msg = e.getCause().toString();
- }
- return createFailChunk(ERR_EXCEPTION, msg);
+ return createFailChunk(ERR_EXCEPTION, e.getMessage());
}
}
@@ -431,175 +394,4 @@ public class DdmHandleViewDebug extends DdmHandle {
byte[] data = b.toByteArray();
return new Chunk(CHUNK_VUOP, data, 0, data.length);
}
-
- /**
- * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
- * buffer.
- *
- * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
- * be the same length, and will be set to the argument types of the data read.
- *
- * @hide
- */
- @VisibleForTesting
- public static void deserializeMethodParameters(
- Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
- ViewMethodInvocationSerializationException {
- checkArgument(args.length == argTypes.length);
-
- for (int i = 0; i < args.length; i++) {
- char typeSignature = in.getChar();
- boolean isArray = typeSignature == SIG_ARRAY;
- if (isArray) {
- char arrayType = in.getChar();
- if (arrayType != SIG_BYTE) {
- // This implementation only supports byte-arrays for now.
- throw new ViewMethodInvocationSerializationException(
- "Unsupported array parameter type (" + typeSignature
- + ") to invoke view method @argument " + i);
- }
-
- int arrayLength = in.getInt();
- if (arrayLength > in.remaining()) {
- // The sender did not actually sent the specified amount of bytes. This
- // avoids a malformed packet to trigger an out-of-memory error.
- throw new BufferUnderflowException();
- }
-
- byte[] byteArray = new byte[arrayLength];
- in.get(byteArray);
-
- argTypes[i] = byte[].class;
- args[i] = byteArray;
- } else {
- switch (typeSignature) {
- case SIG_BOOLEAN:
- argTypes[i] = boolean.class;
- args[i] = in.get() != 0;
- break;
- case SIG_BYTE:
- argTypes[i] = byte.class;
- args[i] = in.get();
- break;
- case SIG_CHAR:
- argTypes[i] = char.class;
- args[i] = in.getChar();
- break;
- case SIG_SHORT:
- argTypes[i] = short.class;
- args[i] = in.getShort();
- break;
- case SIG_INT:
- argTypes[i] = int.class;
- args[i] = in.getInt();
- break;
- case SIG_LONG:
- argTypes[i] = long.class;
- args[i] = in.getLong();
- break;
- case SIG_FLOAT:
- argTypes[i] = float.class;
- args[i] = in.getFloat();
- break;
- case SIG_DOUBLE:
- argTypes[i] = double.class;
- args[i] = in.getDouble();
- break;
- case SIG_STRING: {
- argTypes[i] = String.class;
- int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
- byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
- in.get(rawStringBuffer);
- args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
- break;
- }
- default:
- Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
- throw new ViewMethodInvocationSerializationException(
- "Unsupported parameter type (" + typeSignature
- + ") to invoke view method.");
- }
- }
-
- }
- }
-
- /**
- * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
- * @hide
- */
- @VisibleForTesting
- public static byte[] serializeReturnValue(Class<?> type, Object value)
- throws ViewMethodInvocationSerializationException, IOException {
- ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
- DataOutputStream dos = new DataOutputStream(byteOutStream);
-
- if (type.isArray()) {
- if (!type.equals(byte[].class)) {
- // Only byte arrays are supported currently.
- throw new ViewMethodInvocationSerializationException(
- "Unsupported array return type (" + type + ")");
- }
- byte[] byteArray = (byte[]) value;
- dos.writeChar(SIG_ARRAY);
- dos.writeChar(SIG_BYTE);
- dos.writeInt(byteArray.length);
- dos.write(byteArray);
- } else if (boolean.class.equals(type)) {
- dos.writeChar(SIG_BOOLEAN);
- dos.write((boolean) value ? 1 : 0);
- } else if (byte.class.equals(type)) {
- dos.writeChar(SIG_BYTE);
- dos.writeByte((byte) value);
- } else if (char.class.equals(type)) {
- dos.writeChar(SIG_CHAR);
- dos.writeChar((char) value);
- } else if (short.class.equals(type)) {
- dos.writeChar(SIG_SHORT);
- dos.writeShort((short) value);
- } else if (int.class.equals(type)) {
- dos.writeChar(SIG_INT);
- dos.writeInt((int) value);
- } else if (long.class.equals(type)) {
- dos.writeChar(SIG_LONG);
- dos.writeLong((long) value);
- } else if (double.class.equals(type)) {
- dos.writeChar(SIG_DOUBLE);
- dos.writeDouble((double) value);
- } else if (float.class.equals(type)) {
- dos.writeChar(SIG_FLOAT);
- dos.writeFloat((float) value);
- } else if (String.class.equals(type)) {
- dos.writeChar(SIG_STRING);
- dos.writeUTF(value != null ? (String) value : "");
- } else {
- dos.writeChar(SIG_VOID);
- }
-
- return byteOutStream.toByteArray();
- }
-
- // Prefixes for simple primitives. These match the JNI definitions.
- private static final char SIG_ARRAY = '[';
- private static final char SIG_BOOLEAN = 'Z';
- private static final char SIG_BYTE = 'B';
- private static final char SIG_SHORT = 'S';
- private static final char SIG_CHAR = 'C';
- private static final char SIG_INT = 'I';
- private static final char SIG_LONG = 'J';
- private static final char SIG_FLOAT = 'F';
- private static final char SIG_DOUBLE = 'D';
- private static final char SIG_VOID = 'V';
- // Prefixes for some commonly used objects
- private static final char SIG_STRING = 'R';
-
- /**
- * @hide
- */
- @VisibleForTesting
- public static class ViewMethodInvocationSerializationException extends Exception {
- ViewMethodInvocationSerializationException(String message) {
- super(message);
- }
- }
}
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index d51e62e709c2..1488cff3a35e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -15,6 +15,8 @@
*/
package android.hardware.biometrics;
+import android.hardware.biometrics.BiometricSourceType;
+
/**
* Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system
* services (e.g. SystemUI) to register a listener for updates about the current state of biometric
@@ -49,4 +51,15 @@ oneway interface AuthenticationStateListener {
* @param userId The user Id for the requested authentication
*/
void onAuthenticationFailed(int requestReason, int userId);
+
+ /**
+ * Defines behavior in response to biometric being acquired.
+ * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+ * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+ * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+ * a known acquired message.
+ */
+ void onAuthenticationAcquired(
+ in BiometricSourceType biometricSourceType, int requestReason, int acquiredInfo
+ );
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 64a62a9299b8..f18a0b758bc5 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -202,8 +202,11 @@ public abstract class DisplayManagerInternal {
/**
* Called by the window manager to perform traversals while holding a
* surface flinger transaction.
+ * @param t The default transaction.
+ * @param displayTransactions The transactions mapped by display id.
*/
- public abstract void performTraversal(Transaction t);
+ public abstract void performTraversal(Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions);
/**
* Tells the display manager about properties of the display that depend on the windows on it.
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5078dc351f6f..46705a31f395 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -29,6 +29,7 @@ import java.util.Objects;
/**
* Encapsulates a collection of attributes describing information about a vibration.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class VibrationAttributes implements Parcelable {
private static final String TAG = "VibrationAttributes";
@@ -463,6 +464,7 @@ public final class VibrationAttributes implements Parcelable {
* Builder class for {@link VibrationAttributes} objects.
* By default, all information is set to UNKNOWN.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Builder {
private int mUsage = USAGE_UNKNOWN;
private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde69917820..bef645698b4a 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -247,6 +248,44 @@ public class ContactKeysManager {
}
/**
+ * Updates a contact key entry's local verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param localVerificationState the new local verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int localVerificationState) {
+ validateVerificationState(localVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Updates a contact key entry's remote verification state that belongs to the caller app.
*
* @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@ public class ContactKeysManager {
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
}
+ /**
+ * Updates a contact key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param lookupKey the value that references the contact
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ @NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ final Bundle extras = new Bundle();
+ extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ final Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+
private static void validateVerificationState(int verificationState) {
if (verificationState != UNVERIFIED
&& verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@ public class ContactKeysManager {
public boolean removeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
- Bundle extras = new Bundle();
+ final Bundle extras = new Bundle();
extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- Bundle response = nullSafeCall(mContentResolver,
+ final Bundle response = nullSafeCall(mContentResolver,
ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@ public class ContactKeysManager {
}
/**
+ * Updates a self key entry's remote verification state that belongs to the app identified
+ * by ownerPackageName.
+ *
+ * @param deviceId an app-specified identifier for the device
+ * @param accountId an app-specified identifier for the account
+ * @param ownerPackageName the package name of the app that owns the key
+ * @param remoteVerificationState the new remote verification state
+ *
+ * @return true if the entry was updated, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+ android.Manifest.permission.WRITE_CONTACTS})
+ public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ @NonNull String accountId,
+ @NonNull String ownerPackageName,
+ @VerificationState int remoteVerificationState) {
+ validateVerificationState(remoteVerificationState);
+
+ Bundle extras = new Bundle();
+ extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+ extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+ Bundle response = nullSafeCall(mContentResolver,
+ ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+ return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ }
+
+ /**
* Maximum size of a contact key.
*/
public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 064bc6947fc4..35b137a322e3 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -314,13 +314,20 @@ public class FocusFinder {
if (count < 2) {
return null;
}
+ View next = null;
+ final boolean[] looped = new boolean[1];
switch (direction) {
case View.FOCUS_FORWARD:
- return getNextFocusable(focused, focusables, count);
+ next = getNextFocusable(focused, focusables, count, looped);
+ break;
case View.FOCUS_BACKWARD:
- return getPreviousFocusable(focused, focusables, count);
+ next = getPreviousFocusable(focused, focusables, count, looped);
+ break;
}
- return focusables.get(count - 1);
+ if (root != null && root.mAttachInfo != null && root == root.getRootView()) {
+ root.mAttachInfo.mNextFocusLooped = looped[0];
+ }
+ return next != null ? next : focusables.get(count - 1);
}
private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
@@ -375,7 +382,8 @@ public class FocusFinder {
return closest;
}
- private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getNextFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -385,10 +393,12 @@ public class FocusFinder {
return focusables.get(position + 1);
}
}
+ outLooped[0] = true;
return focusables.get(0);
}
- private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count,
+ boolean[] outLooped) {
if (count < 2) {
return null;
}
@@ -398,6 +408,7 @@ public class FocusFinder {
return focusables.get(position - 1);
}
}
+ outLooped[0] = true;
return focusables.get(count - 1);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050e41d4..99863d013970 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@ interface IWindowManager
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+ @EnforcePermission("DETECT_SCREEN_RECORDING")
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 000000000000..ee55737cc27e
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+ private static ScreenRecordingCallbacks sInstance;
+ private static final Object sLock = new Object();
+
+ private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+ new ArrayMap<>();
+
+ private IScreenRecordingCallback mCallbackNotifier;
+ private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+ private ScreenRecordingCallbacks() {}
+
+ private static @NonNull IWindowManager getWindowManagerService() {
+ return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+ }
+
+ static ScreenRecordingCallbacks getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ScreenRecordingCallbacks();
+ }
+ return sInstance;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ @ScreenRecordingState
+ int addCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ if (mCallbackNotifier == null) {
+ mCallbackNotifier =
+ new IScreenRecordingCallback.Stub() {
+ @Override
+ public void onScreenRecordingStateChanged(
+ boolean visibleInScreenRecording) {
+ int state =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ notifyCallbacks(state);
+ }
+ };
+ try {
+ boolean visibleInScreenRecording =
+ getWindowManagerService()
+ .registerScreenRecordingCallback(mCallbackNotifier);
+ mState =
+ visibleInScreenRecording
+ ? SCREEN_RECORDING_STATE_VISIBLE
+ : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ mCallbacks.put(callback, executor);
+ return mState;
+ }
+ }
+
+ @RequiresPermission(DETECT_SCREEN_RECORDING)
+ void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ synchronized (sLock) {
+ mCallbacks.remove(callback);
+ if (mCallbacks.isEmpty()) {
+ try {
+ getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mCallbackNotifier = null;
+ }
+ }
+ }
+
+ private void notifyCallbacks(@ScreenRecordingState int state) {
+ List<Runnable> callbacks;
+ synchronized (sLock) {
+ mState = state;
+ if (mCallbacks.isEmpty()) {
+ return;
+ }
+
+ callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ Consumer<Integer> callback = mCallbacks.keyAt(i);
+ Executor executor = mCallbacks.valueAt(i);
+ callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+ }
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).run();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b22fda9c31a..2366ff77692b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+ private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
+ private int mInfrequentUpdateCount = 0;
+ private long mLastUpdateTimeMillis = 0;
+ private long mMinusOneFrameIntervalMillis = 0;
+ private long mMinusTwoFrameIntervalMillis = 0;
+ private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -20253,7 +20263,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20371,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
// For VRR to vote the preferred frame rate
- votePreferredFrameRate();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ updateInfrequentCount();
+ votePreferredFrameRate();
+ }
mParent.onDescendantInvalidated(this, this);
}
}
@@ -31336,6 +31352,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
+ * Indicates if the next focus will be looped back to the first focusable view of the entire
+ * hierarchy when finding in the direction of {@link #FOCUS_FORWARD} or to the last
+ * focusable view when finding in the direction of {@link #FOCUS_BACKWARD}.
+ */
+ boolean mNextFocusLooped = false;
+
+ /**
* The id of the window for accessibility purposes.
*/
int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -33124,11 +33147,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private int calculateFrameRateCategory(float sizePercentage) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
+ if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+ < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ } else {
+ return FRAME_RATE_CATEGORY_HIGH;
+ }
+ }
+
+ if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
return FRAME_RATE_CATEGORY_NORMAL;
}
+
+ return mLastFrameRateCategory;
}
private void votePreferredFrameRate() {
@@ -33137,22 +33169,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ mLastFrameRateCategory = frameRateCateogry;
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
viewRootImpl.recordViewPercentage(sizePercentage);
}
@@ -33231,4 +33263,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return 0;
}
+
+ /**
+ * This function is mainly used for migrating infrequent layer lagic
+ * from SurfaceFlinger to Toolkit.
+ * The infrequent layter logic includes:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ private void updateInfrequentCount() {
+ long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+ long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+ mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+ mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+ mLastUpdateTimeMillis = currentTimeMillis;
+ if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+ mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+ ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+ } else {
+ mInfrequentUpdateCount = 0;
+ }
+ }
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 25e0eca12bf3..4f1fb40ab214 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
+ * Unless required by applicable law or agreed to in writing, softwareViewDebug
* 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
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -34,10 +36,13 @@ import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+import com.android.internal.annotations.VisibleForTesting;
+
import libcore.util.HexEncoding;
import java.io.BufferedOutputStream;
@@ -54,9 +59,11 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
@@ -67,7 +74,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -76,6 +82,9 @@ import java.util.stream.Stream;
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
*/
public class ViewDebug {
+
+ private static final String TAG = "ViewDebug";
+
/**
* @deprecated This flag is now unused
*/
@@ -425,6 +434,7 @@ public class ViewDebug {
private static final String REMOTE_PROFILE = "PROFILE";
private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+ private static final String REMOTE_COMMAND_INVOKE_METHOD = "INVOKE_METHOD";
private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
@@ -555,6 +565,8 @@ public class ViewDebug {
requestLayout(view, params[0]);
} else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
profile(view, clientStream, params[0]);
+ } else if (REMOTE_COMMAND_INVOKE_METHOD.equals(command)) {
+ invokeViewMethod(view, clientStream, params);
}
}
}
@@ -1825,46 +1837,84 @@ public class ViewDebug {
Log.d(tag, sb.toString());
}
+ private static void invokeViewMethod(View root, OutputStream clientStream, String[] params)
+ throws IOException {
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+ try {
+ if (params.length < 2) {
+ throw new IllegalArgumentException("Missing parameter");
+ }
+ View targetView = findView(root, params[0]);
+ if (targetView == null) {
+ throw new IllegalArgumentException("View not found: " + params[0]);
+ }
+ String method = params[1];
+ ByteBuffer args = ByteBuffer.wrap(params.length < 2
+ ? new byte[0]
+ : Base64.decode(params[2], Base64.NO_WRAP));
+ byte[] result = invokeViewMethod(targetView, method, args);
+ out.write("1");
+ out.newLine();
+ out.write(Base64.encodeToString(result, Base64.NO_WRAP));
+ out.newLine();
+ } catch (Exception e) {
+ out.write("-1");
+ out.newLine();
+ out.write(e.getMessage());
+ out.newLine();
+ } finally {
+ out.close();
+ }
+ }
+
/**
* Invoke a particular method on given view.
* The given method is always invoked on the UI thread. The caller thread will stall until the
* method invocation is complete. Returns an object equal to the result of the method
* invocation, null if the method is declared to return void
+ * @param params all the method parameters encoded in a byteArray
* @throws Exception if the method invocation caused any exception
* @hide
*/
- public static Object invokeViewMethod(final View view, final Method method,
- final Object[] args) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AtomicReference<Object> result = new AtomicReference<Object>();
- final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
-
- view.post(new Runnable() {
- @Override
- public void run() {
- try {
- result.set(method.invoke(view, args));
- } catch (InvocationTargetException e) {
- exception.set(e.getCause());
- } catch (Exception e) {
- exception.set(e);
- }
+ public static byte[] invokeViewMethod(View targetView, String methodName, ByteBuffer params)
+ throws ViewMethodInvocationSerializationException {
+ Class<?>[] argTypes;
+ Object[] args;
+ if (!params.hasRemaining()) {
+ argTypes = new Class<?>[0];
+ args = new Object[0];
+ } else {
+ int nArgs = params.getInt();
+ argTypes = new Class<?>[nArgs];
+ args = new Object[nArgs];
- latch.countDown();
- }
- });
+ deserializeMethodParameters(args, argTypes, params);
+ }
+ Method method;
try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ method = targetView.getClass().getMethod(methodName, argTypes);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "No such method: " + e.getMessage());
+ throw new ViewMethodInvocationSerializationException(
+ "No such method: " + e.getMessage());
}
- if (exception.get() != null) {
- throw new RuntimeException(exception.get());
+ try {
+ // Invoke the method on Views handler
+ FutureTask<Object> task = new FutureTask<>(() -> method.invoke(targetView, args));
+ targetView.post(task);
+ Object result = task.get();
+ Class<?> returnType = method.getReturnType();
+ return serializeReturnValue(returnType, returnType.cast(result));
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+ String msg = e.getCause().getMessage();
+ if (msg == null) {
+ msg = e.getCause().toString();
+ }
+ throw new RuntimeException(msg);
}
-
- return result.get();
}
/**
@@ -1961,4 +2011,175 @@ public class ViewDebug {
*/
Bitmap createBitmap();
}
+
+ /**
+ * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+ * buffer.
+ *
+ * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+ * be the same length, and will be set to the argument types of the data read.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static void deserializeMethodParameters(
+ Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+ ViewMethodInvocationSerializationException {
+ checkArgument(args.length == argTypes.length);
+
+ for (int i = 0; i < args.length; i++) {
+ char typeSignature = in.getChar();
+ boolean isArray = typeSignature == SIG_ARRAY;
+ if (isArray) {
+ char arrayType = in.getChar();
+ if (arrayType != SIG_BYTE) {
+ // This implementation only supports byte-arrays for now.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array parameter type (" + typeSignature
+ + ") to invoke view method @argument " + i);
+ }
+
+ int arrayLength = in.getInt();
+ if (arrayLength > in.remaining()) {
+ // The sender did not actually sent the specified amount of bytes. This
+ // avoids a malformed packet to trigger an out-of-memory error.
+ throw new BufferUnderflowException();
+ }
+
+ byte[] byteArray = new byte[arrayLength];
+ in.get(byteArray);
+
+ argTypes[i] = byte[].class;
+ args[i] = byteArray;
+ } else {
+ switch (typeSignature) {
+ case SIG_BOOLEAN:
+ argTypes[i] = boolean.class;
+ args[i] = in.get() != 0;
+ break;
+ case SIG_BYTE:
+ argTypes[i] = byte.class;
+ args[i] = in.get();
+ break;
+ case SIG_CHAR:
+ argTypes[i] = char.class;
+ args[i] = in.getChar();
+ break;
+ case SIG_SHORT:
+ argTypes[i] = short.class;
+ args[i] = in.getShort();
+ break;
+ case SIG_INT:
+ argTypes[i] = int.class;
+ args[i] = in.getInt();
+ break;
+ case SIG_LONG:
+ argTypes[i] = long.class;
+ args[i] = in.getLong();
+ break;
+ case SIG_FLOAT:
+ argTypes[i] = float.class;
+ args[i] = in.getFloat();
+ break;
+ case SIG_DOUBLE:
+ argTypes[i] = double.class;
+ args[i] = in.getDouble();
+ break;
+ case SIG_STRING: {
+ argTypes[i] = String.class;
+ int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+ byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+ in.get(rawStringBuffer);
+ args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+ break;
+ }
+ default:
+ Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported parameter type (" + typeSignature
+ + ") to invoke view method.");
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+ * @hide
+ */
+ @VisibleForTesting
+ public static byte[] serializeReturnValue(Class<?> type, Object value)
+ throws ViewMethodInvocationSerializationException, IOException {
+ ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+ DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+ if (type.isArray()) {
+ if (!type.equals(byte[].class)) {
+ // Only byte arrays are supported currently.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array return type (" + type + ")");
+ }
+ byte[] byteArray = (byte[]) value;
+ dos.writeChar(SIG_ARRAY);
+ dos.writeChar(SIG_BYTE);
+ dos.writeInt(byteArray.length);
+ dos.write(byteArray);
+ } else if (boolean.class.equals(type)) {
+ dos.writeChar(SIG_BOOLEAN);
+ dos.write((boolean) value ? 1 : 0);
+ } else if (byte.class.equals(type)) {
+ dos.writeChar(SIG_BYTE);
+ dos.writeByte((byte) value);
+ } else if (char.class.equals(type)) {
+ dos.writeChar(SIG_CHAR);
+ dos.writeChar((char) value);
+ } else if (short.class.equals(type)) {
+ dos.writeChar(SIG_SHORT);
+ dos.writeShort((short) value);
+ } else if (int.class.equals(type)) {
+ dos.writeChar(SIG_INT);
+ dos.writeInt((int) value);
+ } else if (long.class.equals(type)) {
+ dos.writeChar(SIG_LONG);
+ dos.writeLong((long) value);
+ } else if (double.class.equals(type)) {
+ dos.writeChar(SIG_DOUBLE);
+ dos.writeDouble((double) value);
+ } else if (float.class.equals(type)) {
+ dos.writeChar(SIG_FLOAT);
+ dos.writeFloat((float) value);
+ } else if (String.class.equals(type)) {
+ dos.writeChar(SIG_STRING);
+ dos.writeUTF(value != null ? (String) value : "");
+ } else {
+ dos.writeChar(SIG_VOID);
+ }
+
+ return byteOutStream.toByteArray();
+ }
+
+ // Prefixes for simple primitives. These match the JNI definitions.
+ private static final char SIG_ARRAY = '[';
+ private static final char SIG_BOOLEAN = 'Z';
+ private static final char SIG_BYTE = 'B';
+ private static final char SIG_SHORT = 'S';
+ private static final char SIG_CHAR = 'C';
+ private static final char SIG_INT = 'I';
+ private static final char SIG_LONG = 'J';
+ private static final char SIG_FLOAT = 'F';
+ private static final char SIG_DOUBLE = 'D';
+ private static final char SIG_VOID = 'V';
+ // Prefixes for some commonly used objects
+ private static final char SIG_STRING = 'R';
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class ViewMethodInvocationSerializationException extends Exception {
+ ViewMethodInvocationSerializationException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c18aeee52460..c27b2b174587 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1025,6 +1025,13 @@ public final class ViewRootImpl implements ViewParent,
// time for revaluating the idle status before lowering the frame rate.
private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+ /*
+ * the variables below are used to determine whther a dVRR feature should be enabled
+ */
+
+ // Used to determine whether to suppress boost on typing
+ private boolean mShouldSuppressBoostOnTyping = false;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -7283,8 +7290,18 @@ public final class ViewRootImpl implements ViewParent,
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
+ mAttachInfo.mNextFocusLooped = false;
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
+ if (mAttachInfo.mNextFocusLooped) {
+ // The next focus is looped. Let's try to move the focus to the adjacent
+ // window. Note: we still need to move the focus in this window
+ // regardless of what moveFocusToAdjacentWindow returns, so the focus
+ // can be looped back from the focus in the adjacent window to next
+ // focus of this window.
+ moveFocusToAdjacentWindow(direction);
+ }
+
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
@@ -12275,7 +12292,7 @@ public final class ViewRootImpl implements ViewParent,
boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
|| motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP;
- boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+ boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
// use toolkitSetFrameRate flag to gate the change
return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
&& getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c78826116426..38cf4907cbe7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@ import android.window.TrustedPresentationThresholds;
import com.android.window.flags.Flags;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -6117,4 +6119,65 @@ public interface WindowManager extends ViewManager {
throw new UnsupportedOperationException(
"getDefaultToken is not implemented");
}
+
+ /** @hide */
+ @Target(ElementType.TYPE_USE)
+ @IntDef(
+ prefix = {"SCREEN_RECORDING_STATE"},
+ value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ScreenRecordingState {}
+
+ /** Indicates the app that registered the callback is not visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+ /** Indicates the app that registered the callback is visible in screen recording. */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+ /**
+ * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+ * visible in screen recording or was visible in screen recording and becomes invisible in
+ * screen recording.
+ *
+ * <p>An app is considered visible in screen recording if any activities owned by the
+ * registering process's UID are being recorded.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * windowManager.addScreenRecordingCallback(state -> {
+ * // handle change in screen recording state
+ * });
+ * </pre>
+ *
+ * @param executor The executor on which callback method will be invoked.
+ * @param callback The callback that will be invoked when screen recording visibility changes.
+ * @return the current screen recording state.
+ * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+ * @see #SCREEN_RECORDING_STATE_VISIBLE
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a screen recording callback.
+ *
+ * @param callback The callback to remove.
+ * @see #addScreenRecordingCallback(Executor, Consumer)
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+ default void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad755cba..eaf45c488634 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@ import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.window.WindowProviderService.isWindowProviderService;
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -551,4 +553,25 @@ public final class WindowManagerImpl implements WindowManager {
public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
+
+ @Override
+ public @ScreenRecordingState int addScreenRecordingCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+ return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+ }
+ return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+ }
+
+ @Override
+ public void removeScreenRecordingCallback(
+ @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+ if (screenRecordingCallbacks()) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ef1bf5a5c548..146b576a8d1f 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2424,7 +2424,7 @@ public final class AccessibilityManager {
}
}
try {
- service.attachAccessibilityOverlayToDisplay_enforcePermission(
+ service.attachAccessibilityOverlayToDisplay(
displayId, surfaceControl);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1c5d29e0ff1e..eca15869f581 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -139,5 +139,5 @@ interface IAccessibilityManager {
WindowTransformationSpec getWindowTransformationSpec(int windowId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
- void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
+ void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e08697..9d613bcae29a 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@ flag {
description: "Feature flag for toolkit metrics collecting for frame rate decision"
bug: "301343249"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_default_normal_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category as NORMAL for default"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_by_size_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate category based on size"
+ bug: "239979904"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_velocity_mapping_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for setting frame rate based on velocity"
+ bug: "239979904"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d27cb37..2f765ae6de38 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.Compatibility;
@@ -47,8 +46,7 @@ public final class URLUtil {
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
- public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+ static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
private static final String LOGTAG = "webkit";
private static final boolean TRACE = false;
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
new file mode 100644
index 000000000000..9f0b7c3dfb02
--- /dev/null
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+ name: "notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49eef0b..5cda3f2b2bc0 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.RemotableViewMethod;
import android.widget.Button;
import android.widget.RemoteViews;
@@ -43,6 +57,14 @@ public class EmphasizedNotificationButton extends Button {
private final GradientDrawable mBackground;
private boolean mPriority;
+ private int mInitialDrawablePadding;
+ private int mIconSize;
+
+ private Drawable mIconToGlue;
+ private CharSequence mLabelToGlue;
+ private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+ private boolean mGluePending;
+
public EmphasizedNotificationButton(Context context) {
this(context, null);
}
@@ -58,10 +80,25 @@ public class EmphasizedNotificationButton extends Button {
public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
mRipple = (RippleDrawable) getBackground();
mRipple.mutate();
DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
mBackground = (GradientDrawable) inset.getDrawable();
+
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_icon_drawable_size);
+
+ try (TypedArray typedArray = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+ mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+ android.R.styleable.TextView_drawablePadding, 0);
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+ }
}
@RemotableViewMethod
@@ -95,19 +132,248 @@ public class EmphasizedNotificationButton extends Button {
return () -> setImageDrawable(drawable);
}
- private void setImageDrawable(Drawable drawable) {
+ private void setImageDrawable(@Nullable Drawable drawable) {
if (drawable != null) {
- drawable.mutate();
- drawable.setTintList(getTextColors());
- drawable.setTintBlendMode(BlendMode.SRC_IN);
- int iconSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_actions_icon_drawable_size);
- drawable.setBounds(0, 0, iconSize, iconSize);
+ prepareIcon(drawable);
}
setCompoundDrawablesRelative(drawable, null, null, null);
}
/**
+ * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+ * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+ * the latter to work.
+ *
+ * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+ * button is displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueIconAsync")
+ public void glueIcon(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ setIconToGlue(drawable);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueIconAsync(@Nullable Icon icon) {
+ final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+ return () -> setIconToGlue(drawable);
+ }
+
+ private void setIconToGlue(@Nullable Drawable icon) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+ return;
+ }
+
+ prepareIcon(icon);
+
+ mIconToGlue = icon;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void prepareIcon(@NonNull Drawable drawable) {
+ drawable.mutate();
+ drawable.setTintList(getTextColors());
+ drawable.setTintBlendMode(BlendMode.SRC_IN);
+ drawable.setBounds(0, 0, mIconSize, mIconSize);
+ }
+
+ /**
+ * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+ * with the text if the button is wider than needed and the text isn't start-aligned.
+ *
+ * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+ * displayed.
+ */
+ @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+ public void glueLabel(@Nullable CharSequence label) {
+ setLabelToGlue(label);
+ }
+
+ /**
+ * @hide
+ */
+ @RemotableViewMethod
+ public Runnable glueLabelAsync(@Nullable CharSequence label) {
+ return () -> setLabelToGlue(label);
+ }
+
+ private void setLabelToGlue(@Nullable CharSequence label) {
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+ return;
+ }
+
+ mLabelToGlue = label;
+ mGluePending = true;
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+ + "gluedLayoutDirection = " + mGluedLayoutDirection);
+ }
+
+ if (layoutDirection != mGluedLayoutDirection) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+ }
+ mGluePending = true;
+ }
+
+ glueIconAndLabelIfNeeded();
+ }
+
+ private void glueIconAndLabelIfNeeded() {
+ // Don't need to glue:
+
+ if (!mGluePending) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+ }
+ return;
+ }
+
+ if (mIconToGlue == null && mLabelToGlue == null) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+ }
+ mGluePending = false;
+ return;
+ }
+
+ if (!USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+ return;
+ }
+
+ // Not ready to glue yet:
+
+ if (!isLayoutDirectionResolved()) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "glueIconAndLabelIfNeeded: "
+ + "layout direction not resolved; doing nothing");
+ }
+ return;
+ }
+
+ // Ready to glue but don't have an icon *and* a label:
+ //
+ // (Note that this will *not* happen while the button is being initialized, since we won't
+ // be ready to glue. This can only happen if the button is initialized and displayed and
+ // *then* someone calls glueIcon or glueLabel.
+
+ if (mIconToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+ return;
+ }
+
+ if (mLabelToGlue == null) {
+ Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+ return;
+ }
+
+ // Can't glue:
+
+ final int layoutDirection = getLayoutDirection();
+ if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+ Log.e(TAG, "glueIconAndLabelIfNeeded: "
+ + "resolved layout direction neither LTR nor RTL; "
+ + "doing nothing");
+ return;
+ }
+
+ // No excuses left, let's glue it!
+
+ glueIconAndLabel(layoutDirection);
+
+ mGluePending = false;
+ mGluedLayoutDirection = layoutDirection;
+ }
+
+ // Unicode replacement character
+ private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+ // Unicode no-break space
+ private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+ private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+ private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+ private static final String FIRST_STRONG_ISOLATE = "\u2068";
+ private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+ private void glueIconAndLabel(int layoutDirection) {
+ final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "glueIconAndLabel: "
+ + "icon = " + mIconToGlue + ", "
+ + "iconSize = " + mIconSize + "px, "
+ + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+ + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+ + "rtlLayout = " + rtlLayout);
+ }
+
+ logIfTextDirectionNotFirstStrong();
+
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+ // The text direction of the label might not match the layout direction of the button, so
+ // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+ // layout direction. This puts the icon, padding, and label in the right order.
+ builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+ appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+ appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+ // If the text and layout directions are different, we would end up with the *label* in the
+ // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+ // automatic text direction heuristic that Android uses by default.
+ builder.append(FIRST_STRONG_ISOLATE);
+
+ appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+ builder.append(POP_DIRECTIONAL_ISOLATE);
+
+ setText(builder);
+ }
+
+ private void logIfTextDirectionNotFirstStrong() {
+ if (!isTextDirectionResolved()) {
+ Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+ + "letting View assume FIRST STRONG");
+ }
+ final int textDirection = getTextDirection();
+ if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+ Log.w(TAG, "glueIconAndLabel: "
+ + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+ + "but found " + textDirection + "; "
+ + "will use a FIRST STRONG ISOLATE regardless");
+ }
+ }
+
+ private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+ final int spanStart = builder.length();
+ builder.append(text);
+ final int spanEnd = builder.length();
+ builder.setSpan(span, spanStart, spanEnd, 0);
+ }
+
+ /**
* Sets whether this view is a priority over its peers (which affects width).
* Specifically, this is used by {@link NotificationActionListLayout} to give this view width
* priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@ public class EmphasizedNotificationButton extends Button {
public boolean isPriority() {
return mPriority;
}
+
+ private static class SpacerSpan extends ReplacementSpan {
+ private int mWidth;
+
+ SpacerSpan(int width) {
+ mWidth = width;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "width = " + mWidth + "px");
+ }
+ }
+
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+ @Nullable Paint.FontMetricsInt fontMetrics) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "getSize returning " + mWidth + "px");
+ }
+
+ return mWidth;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+ float x, int top, int y, int bottom, @NonNull Paint paint) {
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "drawing nothing");
+ }
+
+ // Draw nothing, it's a spacer.
+ }
+
+ private static final String TAG = "SpacerSpan";
+ }
+
+ private static class CenterBesideImageSpan extends MetricAffectingSpan {
+ private int mImageHeight;
+
+ private boolean mMeasured;
+ private int mBaselineShiftOffset;
+
+ CenterBesideImageSpan(int imageHeight) {
+ mImageHeight = imageHeight;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+ }
+ }
+
+ @Override
+ public void updateMeasureState(@NonNull TextPaint textPaint) {
+ final int textHeight = (int) -textPaint.ascent();
+
+ /*
+ * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+ * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+ */
+ if (textHeight < mImageHeight) {
+ mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+ } else {
+ mBaselineShiftOffset = 0;
+ }
+
+ mMeasured = true;
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.d(TAG, "updateMeasureState: "
+ + "imageHeight = " + mImageHeight + "px, "
+ + "textHeight = " + textHeight + "px, "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint textPaint) {
+ if (textPaint == null) {
+ Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+ return;
+ }
+
+ if (!mMeasured) {
+ Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "updateDrawState: "
+ + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+ }
+
+ textPaint.baselineShift += mBaselineShiftOffset;
+ }
+
+ private static final String TAG = "CenterBesideImageSpan";
+ }
+
+ private static final String TAG = "EmphasizedNotificationButton";
}
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9e43fb..69d254499ef4 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
package com.android.internal.widget;
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
import android.annotation.DimenRes;
import android.app.Notification;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -41,13 +45,13 @@ import java.util.Comparator;
*/
@RemoteViews.RemoteView
public class NotificationActionListLayout extends LinearLayout {
-
private final int mGravity;
private int mTotalWidth = 0;
private int mExtraStartPadding = 0;
private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
private boolean mEmphasizedMode;
+ private boolean mEvenlyDividedMode;
private int mDefaultPaddingBottom;
private int mDefaultPaddingTop;
private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@ public class NotificationActionListLayout extends LinearLayout {
}
}
+ private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+ final int numChildren = getChildCount();
+ int childMarginSum = 0;
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ childMarginSum += lp.leftMargin + lp.rightMargin;
+ }
+ }
+
+ final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+ final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "measuring evenly divided width: "
+ + "numChildren = " + numChildren + ", "
+ + "innerWidth = " + innerWidth + "px, "
+ + "childMarginSum = " + childMarginSum + "px, "
+ + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+ + "childWidth = " + childWidth + "px, "
+ + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+ }
+
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.measure(childWidthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ return innerWidth;
+ }
+
private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
boolean collapsePriorityActions) {
final int numChildren = getChildCount();
@@ -208,11 +248,16 @@ public class NotificationActionListLayout extends LinearLayout {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
countAndRebuildMeasureOrder();
final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
- int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- false /* collapsePriorityButtons */);
- if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ int usedWidth;
+ if (mEvenlyDividedMode) {
+ usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+ } else {
usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
- true /* collapsePriorityButtons */);
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
}
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@ public class NotificationActionListLayout extends LinearLayout {
}
/**
+ * Sets whether the available width should be distributed evenly among the action buttons.
+ *
+ * When enabled, the available width (after subtracting this layout's padding and all of the
+ * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+ * to that exact width, even if it is less <em>or more</em> width than they need.
+ *
+ * When disabled, the available width is allocated as buttons need; if that exceeds the
+ * available width, priority buttons are collapsed to just their icon to save space.
+ *
+ * @param evenlyDividedMode whether to enable evenly divided mode
+ */
+ @RemotableViewMethod
+ public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+ if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+ Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+ + "leaving evenly divided mode disabled");
+ return;
+ }
+
+ if (evenlyDividedMode == mEvenlyDividedMode) {
+ return;
+ }
+
+ if (DEBUG_NEW_ACTION_LAYOUT) {
+ Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+ + "requesting layout");
+ }
+ mEvenlyDividedMode = evenlyDividedMode;
+ requestLayout();
+ }
+
+ /**
* Set whether the list is in a mode where some actions are emphasized. This will trigger an
* equal measuring where all actions are full height and change a few parameters like
* the padding.
@@ -410,4 +487,5 @@ public class NotificationActionListLayout extends LinearLayout {
}
}
+ private static final String TAG = "NotificationActionListLayout";
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0171f584a838..6be1be4a765e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
+ <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+ <p>This permission is only granted to system apps.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ @FlaggedApi("android.provider.user_keys")
+ -->
+ <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+ android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to set default account for new contacts.
<p> This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
android:description="@string/permdesc_detectScreenCapture"
android:protectionLevel="normal" />
+ <!-- Allows an application to get notified when it is being recorded.
+ <p>Protection level: normal
+ @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+ android:protectionLevel="normal" />
+
<!-- ======================================== -->
<!-- Permissions for factory reset protection -->
<!-- ======================================== -->
@@ -3188,6 +3208,14 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
<!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
<permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
android:protectionLevel="signature|role" />
@@ -7828,7 +7856,7 @@
android:protectionLevel="normal"/>
<!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
- Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ Gives applications with a <b>major use case</b> of backing-up or syncing content increased
job execution allowance in order to complete the related work. The jobs must have a valid
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
diff --git a/core/res/res/drawable/ic_satellite_alt_24px.xml b/core/res/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 000000000000..f9ca7dc66798
--- /dev/null
+++ b/core/res/res/drawable/ic_satellite_alt_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 558bae727415..be96cc2d6362 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
<!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
<string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
+ <!-- Title of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+ <!-- Summary of the cellular network security safety center source's status. -->
+ <string name="scCellularNetworkSecuritySummary">Review settings</string>
+ <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+ <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+ <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+ <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+ <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+ <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+ <!-- Summary of the safety center issue and notification when a connected network is not using encryption. -->
+ <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+ <!-- Label for the button that links to the cellular network security settings. -->
+ <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+ <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+ <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
<!-- android.net.http Error strings --> <skip />
<!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
<string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
<string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
@@ -6386,4 +6417,14 @@ ul.</string>
<string name="redacted_notification_message"></string>
<!-- Notification action title used instead of a notification's normal title sensitive [CHAR_LIMIT=NOTIF_BODY] -->
<string name="redacted_notification_action_title"></string>
+
+ <!-- Satellite related messages -->
+ <!-- Notification title when satellite service is connected. -->
+ <string name="satellite_notification_title">Auto connected to satellite</string>
+ <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Invoke "What to expect" dialog of messaging application -->
+ <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>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8b74cbfce5e3..603b9026aeb7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
<java-symbol type="string" name="roamingText8" />
<java-symbol type="string" name="roamingText9" />
<java-symbol type="string" name="roamingTextSearching" />
+ <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+ <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+ <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+ <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+ <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+ <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
<java-symbol type="string" name="selected" />
<java-symbol type="string" name="sendText" />
<java-symbol type="string" name="sending" />
@@ -5329,4 +5339,11 @@
<java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
<java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
+
+ <!-- System notification for satellite service -->
+ <java-symbol type="string" name="satellite_notification_title" />
+ <java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_open_message" />
+ <java-symbol type="string" name="satellite_notification_how_it_works" />
+ <java-symbol type="drawable" name="ic_satellite_alt_24px" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f47679991418..1b25d7fa32a4 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -203,12 +203,12 @@ android_ravenwood_test {
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"flag-junit",
- "mockito_ravenwood",
"platform-test-annotations",
"flag-junit",
"testng",
],
srcs: [
+ "src/android/app/ActivityManagerTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
new file mode 100644
index 000000000000..d930e4d79a3a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testSimple() throws Exception {
+ assertTrue(ActivityManager.isSystemReady());
+ assertFalse(ActivityManager.isUserAMonkey());
+ assertNotEquals(UserHandle.USER_NULL, ActivityManager.getCurrentUser());
+ }
+
+ @Test
+ public void testCapabilities() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.getCapabilitiesSummary(~0));
+ ActivityManager.printCapabilitiesFull(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new StringBuilder(), ~0);
+ }
+
+ @Test
+ public void testProcState() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
+ assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
+ assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateConsideredInteraction(PROCESS_STATE_SERVICE));
+ }
+
+ @Test
+ public void testStartResult() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertTrue(ActivityManager.isStartResultSuccessful(50));
+ assertTrue(ActivityManager.isStartResultFatalError(-50));
+ }
+
+ @Test
+ public void testRestrictionLevel() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.restrictionLevelToName(
+ ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ }
+}
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
deleted file mode 100644
index c8be1919fb93..000000000000
--- a/core/tests/coretests/src/android/ddm/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-michschn@google.com
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 0bac1c728f3b..1ad71da7c2d1 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -28,15 +28,20 @@ import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class HandlerThreadTest {
private static final int TEST_WHAT = 1;
- @Rule
+ @Rule(order = 1)
+ public ExpectedException mThrown = ExpectedException.none();
+
+ @Rule(order = 2)
public final RavenwoodRule mRavenwood = new RavenwoodRule();
private boolean mGotMessage = false;
@@ -112,4 +117,28 @@ public class HandlerThreadTest {
assertTrue(mGotMessage);
assertEquals(TEST_WHAT, mGotMessageWhat);
}
+
+ /**
+ * Confirm that a background handler thread throwing an exception during a test results in a
+ * test failure being reported.
+ */
+ @Test
+ public void testUncaughtExceptionFails() throws Exception {
+ // For the moment we can only test Ravenwood; on a physical device uncaught exceptions
+ // are detected, but reported as test failures at a higher level where we can't inspect
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+ mThrown.expect(IllegalStateException.class);
+
+ final HandlerThread thread = new HandlerThread("HandlerThreadTest");
+ thread.start();
+ thread.getThreadHandler().post(() -> {
+ throw new IllegalStateException();
+ });
+
+ // Wait until we've drained past the message above, then terminate test without throwing
+ // directly; the test harness should notice and report the uncaught exception
+ while (!thread.getThreadHandler().getLooper().getQueue().isIdle()) {
+ SystemClock.sleep(10);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
new file mode 100644
index 000000000000..f5a81c582b28
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -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 android.os;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VibrationAttributesTest {
+ @Test
+ public void testSimple() throws Exception {
+ final VibrationAttributes attr = new VibrationAttributes.Builder()
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .setUsage(VibrationAttributes.USAGE_ALARM)
+ .build();
+
+ assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
+ assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
+ }
+}
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/view/ViewDebugTest.java
index 7248983c741c..45228422b97b 100644
--- a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
+++ b/core/tests/coretests/src/android/view/ViewDebugTest.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package android.ddm;
+package android.view;
-import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
-import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+import static android.view.ViewDebug.deserializeMethodParameters;
+import static android.view.ViewDebug.serializeReturnValue;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
-import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
import android.platform.test.annotations.Presubmit;
+import android.view.ViewDebug.ViewMethodInvocationSerializationException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -39,7 +39,7 @@ import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
-public final class DdmHandleViewDebugTest {
+public final class ViewDebugTest {
// true
private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12498ca..60769c7acd45 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@ package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
@@ -475,8 +476,9 @@ public class ViewRootImplTest {
* Also, mIsFrameRateBoosting should be true when the visibility becomes visible
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
View view = new View(sContext);
attachViewToWindow(view);
ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +509,9 @@ public class ViewRootImplTest {
* <7%: FRAME_RATE_CATEGORY_LOW
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +537,9 @@ public class ViewRootImplTest {
* >=7% : FRAME_RATE_CATEGORY_NORMAL
*/
@Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
View view = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +563,96 @@ public class ViewRootImplTest {
}
/**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_HIGH
+ * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_HIGH
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
+ /**
* Test how values of the frame rate cateogry are aggregated.
* It should take the max value among all of the voted categories per frame.
*/
@@ -701,6 +795,61 @@ public class ViewRootImplTest {
});
}
+ /**
+ * Test the logic of infrequent layer:
+ * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+ * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+ * - otherwise, use the previous category value.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+ final long delay = 200L;
+
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ // Frequent update
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // In transistion from frequent update to infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+
+ // Infrequent update
+ Thread.sleep(delay);
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 2ccee71c8ff7..f5563a710563 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -60,7 +60,6 @@ android_ravenwood_test {
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"frameworks-base-testutils",
"servicestests-utils",
],
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 28734283e9b7..91e620cd4b83 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@ applications that come with the platform
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +572,8 @@ applications that come with the platform
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 2b95f30a5e8a..9a66c0fa9eb9 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@ flag {
description: "Enables new UMO experience for PiP menu"
bug: "307998712"
}
+
+flag {
+ name: "enable_bubble_bar"
+ namespace: "multitasking"
+ description: "Enables the new bubble bar UI for tablets"
+ bug: "286246694"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030ff06e..50a58daa6363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -465,7 +465,7 @@ public class Bubble implements BubbleViewProvider {
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
+ public void cleanupViews() {
cleanupViews(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index aea3ca1576e2..d0db7080a099 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -730,7 +730,7 @@ public class BubbleController implements ConfigurationChangeListener,
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
@@ -1714,8 +1714,7 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 127c7e8d9378..dbfa2606ea06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
@@ -41,6 +40,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.RemovedBubble;
@@ -427,7 +427,7 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -1069,7 +1069,6 @@ public class BubbleData {
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7b5f00..dc271331c8f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -186,6 +187,7 @@ public class BubbleTaskViewHelper {
}
if (mTaskView != null) {
mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
mTaskView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 00d683e861e0..73a9cf456a6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -266,13 +266,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- }
mMenuViewController.hideMenu(false /* animated */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce803c591..b95d258da6fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@ public class BubbleBarLayerView extends FrameLayout
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@ public class BubbleBarLayerView extends FrameLayout
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,44 @@ public class BubbleBarLayerView extends FrameLayout
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble) {
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(bubble::cleanupViews);
+ } else {
+ bubble.cleanupViews();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ };
if (mDragController != null && mDragController.isStuckToDismiss()) {
- mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+ mAnimationHelper.animateDismiss(runnable);
} else {
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ mAnimationHelper.animateCollapse(runnable);
}
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 000000000000..fd91ac0affc5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index a089e81ff6dd..e8728498ad64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -100,6 +100,9 @@ class DesktopTasksController(
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
@@ -148,6 +151,8 @@ class DesktopTasksController(
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -240,6 +245,40 @@ class DesktopTasksController(
}
}
+ /** Move a task with given `taskId` to desktop without decor */
+ fun moveToDesktopWithoutDecor(
+ taskId: Int,
+ wct: WindowContainerTransaction
+ ): Boolean {
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+ moveToDesktopWithoutDecor(task, wct)
+ return true
+ }
+
+ /**
+ * Move a task to desktop without decor
+ */
+ private fun moveToDesktopWithoutDecor(
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+ task.taskId
+ )
+ exitSplitIfApplicable(wct, task)
+ // Bring other apps to front first
+ bringDesktopAppsToFront(task.displayId, wct)
+ addMoveToDesktopChanges(wct, task)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* Move a task to desktop
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348d5aca..3fad28ad232f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@ particular order):
Todo
- Per-feature docs
- Feature flagging
-- Best practices \ No newline at end of file
+- Best practices & patterns \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326eadcd5..9aa5f4ffcd78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@ AIDL interfaces and constants. Currently, all AIDL files, and classes under the
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d962adc9..7070dead9957 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@ developers to jump into a few select files and understand how different componen
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@ In some rare cases, there are base components that can change behavior depending
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4e2a07..438aa768165e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@ building to check the log state (is enabled) before printing the print format st
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
+
+## WindowManager/SurfaceFlinger hierarchy dump
+
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
+```shell
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@ If information should be added to the dump, either:
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6aea2ec..b489fe8ea1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@ Currently, the WMShell library is used to drive the windowing experience on hand
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 000000000000..0e20a8ec275c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e640ba7..30ff6691f503 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@ adb shell dumpsys activity service SystemUIService WMShell
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-``` \ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333facc4..98af930c4486 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@ Flicker tests are tests that perform actions and make assertions on the state in
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac748894432..9d015357b60b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@ the product.
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@ To get the SysUI main thread, you can use the `@Main` annotation.
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
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 238e6b5bf2af..c5a01025dcdd 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
@@ -797,14 +797,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- updateDisplayLayout.run();
+ // Scale PiP on density dpi change, so it appears to be the same size physically.
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
+ if (densityDpiChanged) {
+ final float scale = (float) layout.densityDpi()
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
+ postChangeBounds.set(0, 0,
+ (int) (postChangeBounds.width() * scale),
+ (int) (postChangeBounds.height() * scale));
+ }
- // Resize the PiP bounds to be at the same scale relative to the new size spec. For
- // example, if PiP was resized to 90% of the maximum size on the previous layout,
- // make sure it is 90% of the new maximum size spec.
- postChangeBounds.set(0, 0,
- (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
- (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+ updateDisplayLayout.run();
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -821,10 +827,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.setHasUserResizedPip(true);
mTouchHandler.setUserResizeBounds(postChangeBounds);
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a31beac..0c25f27854bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition.tracing;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
@@ -58,6 +59,15 @@ public class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
mDataSource.trace(ctx -> {
final int handlerId = getHandlerId(handler, ctx);
@@ -97,6 +107,15 @@ public class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +139,19 @@ public class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
- os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
SystemClock.elapsedRealtimeNanos());
os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +170,15 @@ public class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -157,6 +194,15 @@ public class PerfettoTransitionTracer implements TransitionTracer {
}
private void onFlush() {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
+ try {
+ doOnFlush();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doOnFlush() {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
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 a8b39c41e6bb..891eea072c0d 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
@@ -888,7 +888,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 75965d6d68d9..1668e3712462 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -143,7 +143,7 @@ class BubbleViewInfoTest : ShellTestCase() {
bubbleController,
mainExecutor
)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
}
@Test
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index d55d28d469d0..b5f7caaf1b5b 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <sstream>
+
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -40,7 +42,8 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 20> sEnableExtensions{
+// Not all of these are strictly required, but are all enabled if present.
+static std::array<std::string_view, 21> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +64,7 @@ static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+ VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -303,6 +307,15 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
+ if (grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+ VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures =
+ new VkPhysicalDeviceFaultFeaturesEXT;
+ deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+ deviceFaultFeatures->pNext = nullptr;
+ *tailPNext = deviceFaultFeatures;
+ tailPNext = &deviceFaultFeatures->pNext;
+ }
+
// query to get the physical device features
mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
// this looks like it would slow things down,
@@ -405,6 +418,79 @@ void VulkanManager::initialize() {
});
}
+namespace {
+void onVkDeviceFault(const std::string& contextLabel, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ // The final crash string should contain as much differentiating info as possible, up to 1024
+ // bytes. As this final message is constructed, the same information is also dumped to the logs
+ // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+ // statement is always placed first to give context.
+ ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", contextLabel.c_str(), description.c_str());
+ std::stringstream crashMsg;
+ crashMsg << "VK_ERROR_DEVICE_LOST (" << contextLabel;
+
+ if (!addressInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+ crashMsg << ", " << addressInfos.size() << " address info (";
+ for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+ ALOGE(" addressType: %d", (int)addressInfo.addressType);
+ ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress);
+ ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+ crashMsg << addressInfo.addressType << ":"
+ << addressInfo.reportedAddress << ":"
+ << addressInfo.addressPrecision << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorInfos.empty()) {
+ ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+ crashMsg << ", " << vendorInfos.size() << " vendor info (";
+ for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+ ALOGE(" description: %s", vendorInfo.description);
+ ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+ ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+ // Omit descriptions for individual vendor info structs in the crash string, as the
+ // fault code and fault data fields should be enough for clustering, and the verbosity
+ // isn't worth it. Additionally, vendors may just set the general description field of
+ // the overall fault to the description of the first element in this list, and that
+ // overall description will be placed at the end of the crash string.
+ crashMsg << vendorInfo.vendorFaultCode << ":"
+ << vendorInfo.vendorFaultData << ", ";
+ }
+ crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+ crashMsg << ")";
+ }
+
+ if (!vendorBinaryData.empty()) {
+ // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+ ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+ " Stack team if you observe this message).",
+ vendorBinaryData.size());
+ crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+ }
+
+ crashMsg << "): " << description;
+ LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+}
+
+void deviceLostProcRenderThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("RenderThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+void deviceLostProcUploadThread(void* callbackContext, const std::string& description,
+ const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+ const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+ const std::vector<std::byte>& vendorBinaryData) {
+ onVkDeviceFault("UploadThread", description, addressInfos, vendorInfos, vendorBinaryData);
+}
+} // anonymous namespace
+
static void onGrContextReleased(void* context) {
VulkanManager* manager = (VulkanManager*)context;
manager->decStrong((void*)onGrContextReleased);
@@ -430,6 +516,10 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = std::move(getProc);
+ backendContext.fDeviceLostContext = nullptr;
+ backendContext.fDeviceLostProc = (contextType == ContextType::kRenderThread)
+ ? deviceLostProcRenderThread
+ : deviceLostProcUploadThread;
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 461dafb91916..ec1edb8943af 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -219,6 +219,27 @@ public final class AltitudeConverter {
* called on the main thread as data will not be loaded from raw assets. Returns true if a Mean
* Sea Level altitude is added to the {@code location}; otherwise, returns false and leaves the
* {@code location} unchanged.
+ *
+ * <p>Prior calls to {@link #addMslAltitudeToLocation(Context, Location)} off the main thread
+ * are necessary to load data from raw assets. Example code on the main thread is as follows:
+ *
+ * <pre>{@code
+ * if (!mAltitudeConverter.addMslAltitudeToLocation(location)) {
+ * // Queue up only one call off the main thread.
+ * if (mIsAltitudeConverterIdle) {
+ * mIsAltitudeConverterIdle = false;
+ * executeOffMainThread(() -> {
+ * try {
+ * // Load raw assets for next call attempt on main thread.
+ * mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ * } catch (IOException e) {
+ * Log.e(TAG, "Not loading raw assets: " + e);
+ * }
+ * mIsAltitudeConverterIdle = true;
+ * });
+ * }
+ * }
+ * }</pre>
*/
@FlaggedApi(Flags.FLAG_GEOID_HEIGHTS_VIA_ALTITUDE_HAL)
public boolean addMslAltitudeToLocation(@NonNull Location location) {
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index e4dc1521ae70..0613fc655521 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -15,6 +15,9 @@
*/
package android.media;
+import static android.media.audio.Flags.FLAG_SCO_MANAGED_BY_AUDIO;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothProfile;
@@ -174,4 +177,13 @@ public final class BluetoothProfileConnectionInfo implements Parcelable {
public boolean isLeOutput() {
return mIsLeOutput;
}
+
+ /**
+ * Factory method for <code>BluetoothProfileConnectionInfo</code> for an HFP device.
+ */
+ @FlaggedApi(FLAG_SCO_MANAGED_BY_AUDIO)
+ public static @NonNull BluetoothProfileConnectionInfo createHfpInfo() {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.HEADSET, false,
+ -1, false);
+ }
}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 018eaf6dc32f..f1107059111c 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "media_tv"
description: "Enable the TV client-side AD framework."
bug: "303506816"
+}
+
+flag {
+ name: "tiaf_v_apis"
+ namespace: "media_tv"
+ description: "TIAF V3.0 APIs for Android V"
+ bug: "303323657"
} \ No newline at end of file
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 1046d8e9aebb..9742d46b8ae9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,10 +64,8 @@ package android.nfc {
}
public final class NfcAdapter {
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
method public void disableForegroundDispatch(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
@@ -83,6 +81,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setTransactionAllowed(boolean);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 4d56c11d1c39..55506a1a7579 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1204,37 +1204,21 @@ public final class NfcAdapter {
}
}
- /**
- * Disables observe mode to allow the transaction to proceed. See
- * {@link #isObserveModeSupported()} for a description of observe mode and
- * use {@link #disallowTransaction()} to enable observe mode and block
- * transactions again.
- *
- * @return boolean indicating success or failure.
- */
- @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean allowTransaction() {
- try {
- return sService.setObserveMode(false);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
- }
- }
-
/**
- * Signals that the transaction has completed and observe mode may be
- * reenabled. See {@link #isObserveModeSupported()} for a description of
- * observe mode and use {@link #allowTransaction()} to disable observe
- * mode and allow transactions to proceed.
- *
- * @return boolean indicating success or failure.
- */
+ * Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
+ * and simply observe and notify the APDU service of polling loop frames. See
+ * {@link #isObserveModeSupported()} for a description of observe mode.
+ *
+ * @param allowed true disables observe mode to allow the transaction to proceed while false
+ * enables observe mode and does not allow transactions to proceed.
+ *
+ * @return boolean indicating success or failure.
+ */
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean disallowTransaction() {
+ public boolean setTransactionAllowed(boolean allowed) {
try {
- return sService.setObserveMode(true);
+ return sService.setObserveMode(!allowed);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
index 7b235f84f0fa..0100718ffd08 100644
--- a/packages/CredentialManager/res/drawable/more_horiz_24px.xml
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -3,6 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
+ android:contentDescription="@string/more_options_content_description"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index b97e9927e0ed..fdda9ea06ab9 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -28,7 +28,7 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:contentDescription="@string/provider_icon_content_description"
+ android:contentDescription="@string/more_options_content_description"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 261154fe9792..c7c2fda6a489 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -25,7 +25,6 @@
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:contentDescription="@string/dropdown_presentation_more_sign_in_options_text"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index f98164b8788c..82b47a964204 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -172,5 +172,5 @@
<!-- Strings for dropdown presentation. -->
<!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
<string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
- <string name="provider_icon_content_description">Credential provider icon</string>
+ <string name="more_options_content_description">More</string>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 68f1c861d51b..02afc547e3f8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -74,6 +74,8 @@ class RemoteViewsFactory {
setMaxHeightMethodName,
context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_icon_size));
+ remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
+ .providerDisplayName);
val drawableId =
com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
remoteViews.setInt(
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9930cb..f425f52804c5 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
<!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
after parts of the app have been previously moved into the cloud for temporary storage.
"installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
- <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+ <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
<!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
<string name="unarchive_body_text">This app will begin to download in the background</string>
<!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c37e8f..b20117d78230 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Process;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@ public class UnarchiveActivity extends Activity {
String appTitle = pm.getApplicationInfo(mPackageName,
PackageManager.ApplicationInfoFlags.of(
MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
- // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
- // archived apps.
- showDialogFragment(appTitle, "installerTitle");
+ String installerTitle = getResponsibleInstallerTitle(pm,
+ pm.getInstallSourceInfo(mPackageName));
+ showDialogFragment(appTitle, installerTitle);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Invalid packageName: " + e.getMessage());
}
}
+ private String getResponsibleInstallerTitle(PackageManager pm,
+ InstallSourceInfo installSource)
+ throws PackageManager.NameNotFoundException {
+ String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+ ? installSource.getInstallingPackageName()
+ : installSource.getUpdateOwnerPackageName();
+ if (packageName == null) {
+ // Should be unreachable.
+ Log.e(TAG, "Installer not found.");
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return "";
+ }
+ return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+ }
+
@NonNull
private String[] getRequestedPermissions(String callingPackage) {
String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4b3af990c17..2e64212d298a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1340,11 +1340,11 @@
<string name="notice_header" translatable="false"></string>
<!-- Name of the phone device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="default">This phone</string>
+ <string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <string name="media_transfer_this_device_name_tablet">This tablet</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
- <string name="media_transfer_this_device_name" product="tv">@string/tv_media_transfer_default</string>
+ <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 15f33d2cff42..ba9180db0887 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -37,6 +37,7 @@ import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
+import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
+import java.util.Arrays;
import java.util.List;
/**
@@ -63,6 +65,17 @@ public class PhoneMediaDevice extends MediaDevice {
private final DeviceIconUtil mDeviceIconUtil;
+ /** Returns this device name for media transfer. */
+ public static @NonNull String getMediaTransferThisDeviceName(@NonNull Context context) {
+ if (isTv(context)) {
+ return context.getString(R.string.media_transfer_this_device_name_tv);
+ } else if (isTablet()) {
+ return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else {
+ return context.getString(R.string.media_transfer_this_device_name);
+ }
+ }
+
/** Returns the device name for the given {@code routeInfo}. */
public static String getSystemRouteNameFromType(
@NonNull Context context, @NonNull MediaRoute2Info routeInfo) {
@@ -80,7 +93,7 @@ public class PhoneMediaDevice extends MediaDevice {
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
break;
case TYPE_BUILTIN_SPEAKER:
- name = context.getString(R.string.media_transfer_this_device_name);
+ name = getMediaTransferThisDeviceName(context);
break;
case TYPE_HDMI:
name = context.getString(isTv ? R.string.tv_media_transfer_default :
@@ -135,6 +148,11 @@ public class PhoneMediaDevice extends MediaDevice {
&& Flags.enableTvMediaOutputDialog();
}
+ static boolean isTablet() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("tablet");
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb395ca0..6761aa7a1006 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
package com.android.settingslib.volume.data.repository
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
interface AudioRepository {
/** Current [AudioManager.getMode]. */
val mode: StateFlow<Int>
+
+ /**
+ * Ringtone mode.
+ *
+ * @see AudioManager.getRingerModeInternal
+ */
+ val ringerMode: StateFlow<RingerMode>
+
+ /**
+ * Communication device. Emits null when there is no communication device available.
+ *
+ * @see AudioDeviceInfo.getType
+ */
+ val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+ /** State of the [AudioStream]. */
+ suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+ /** Current state of the [AudioStream]. */
+ suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
}
class AudioRepositoryImpl(
+ private val context: Context,
private val audioManager: AudioManager,
- backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+ private val coroutineScope: CoroutineScope,
) : AudioRepository {
override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@ class AudioRepositoryImpl(
}
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+ private val audioManagerIntents: SharedFlow<String> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ intent.action?.let { action -> launch { send(action) } }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ for (action in allActions) {
+ addAction(action)
+ }
+ }
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+ override val ringerMode: StateFlow<RingerMode> =
+ audioManagerIntents
+ .filter { ringerActions.contains(it) }
+ .map { RingerMode(audioManager.ringerModeInternal) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ RingerMode(audioManager.ringerModeInternal),
+ )
+
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() =
+ callbackFlow {
+ val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+ audioManager.addOnCommunicationDeviceChangedListener(
+ DirectExecutor.INSTANCE,
+ listener
+ )
+
+ awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+ }
+ .filterNotNull()
+ .map { audioManager.communicationDevice }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ audioManager.communicationDevice,
+ )
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ return audioManagerIntents
+ .filter { modelActions.contains(it) }
+ .map { getCurrentAudioStream(audioStream) }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return withContext(backgroundCoroutineContext) {
+ AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode =
+ audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value)
+ )
+ }
+ }
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ withContext(backgroundCoroutineContext) {
+ audioManager.setStreamVolume(audioStream.value, volume, 0)
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ withContext(backgroundCoroutineContext) {
+ if (isMuted) {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+ } else {
+ audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+ }
+ }
+
+ private fun getMinVolume(stream: AudioStream): Int =
+ try {
+ audioManager.getStreamMinVolume(stream.value)
+ } catch (e: IllegalArgumentException) {
+ // Fallback to STREAM_VOICE_CALL because
+ // CallVolumePreferenceController.java default
+ // return STREAM_VOICE_CALL in getAudioStream
+ audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+ }
+
+ private companion object {
+ val modelActions =
+ setOf(
+ AudioManager.STREAM_MUTE_CHANGED_ACTION,
+ AudioManager.MASTER_MUTE_CHANGED_ACTION,
+ AudioManager.VOLUME_CHANGED_ACTION,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ )
+ val ringerActions =
+ setOf(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ )
+ val allActions = ringerActions + modelActions
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 000000000000..2b12936e8118
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+ val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+ override val isSingleVolume: Boolean
+ get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 000000000000..58f3c2d61f3b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+ init {
+ require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedStreamTypes =
+ setOf(
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_BLUETOOTH_SCO,
+ AudioManager.STREAM_SYSTEM_ENFORCED,
+ AudioManager.STREAM_DTMF,
+ AudioManager.STREAM_TTS,
+ AudioManager.STREAM_ACCESSIBILITY,
+ AudioManager.STREAM_ASSISTANT,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 000000000000..c1be1ee020f2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+ val audioStream: AudioStream,
+ val volume: Int,
+ val minVolume: Int,
+ val maxVolume: Int,
+ val isAffectedByRingerMode: Boolean,
+ val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 000000000000..9f03927264c1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.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.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+ init {
+ require(value in supportedRingerModes) { "Unsupported stream=$value" }
+ }
+
+ private companion object {
+ val supportedRingerModes =
+ setOf(
+ AudioManager.RINGER_MODE_SILENT,
+ AudioManager.RINGER_MODE_VIBRATE,
+ AudioManager.RINGER_MODE_NORMAL,
+ AudioManager.RINGER_MODE_MAX,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7baf6be6..f303ab5fc54b 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@ android_test {
"android.test.runner",
"telephony-common",
"android.test.base",
+ "android.test.mock",
+ "truth",
],
platform_apis: true,
@@ -49,16 +51,23 @@ android_test {
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"flag-junit",
- "mockito-target-minus-junit4",
+ "kotlinx_coroutines_test",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
- "kotlinx_coroutines_test",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
dxflags: ["--multi-dex"],
+ manifest: "AndroidManifest.xml",
}
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca6344c..9fb1c1f65961 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee8822a72..9de601907a62 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Runs Tests for SettingsLib.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsLibTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 000000000000..7b70c64f349b
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor
+ private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+ @Captor
+ private lateinit var communicationDeviceListenerCaptor:
+ ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+ private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+ private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val testScope = TestScope()
+
+ private lateinit var underTest: AudioRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+ `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+ `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+ `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+ triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ }
+ `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ }
+ `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+ volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+ }
+ `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+ isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+ `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+ isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+
+ underTest =
+ AudioRepositoryImpl(
+ context,
+ audioManager,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun audioModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<Int>()
+ underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerModeChange(AudioManager.MODE_IN_CALL)
+ runCurrent()
+
+ assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+ }
+ }
+
+ @Test
+ fun ringerModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<RingerMode>()
+ underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ runCurrent()
+
+ assertThat(modes)
+ .containsExactly(
+ RingerMode(AudioManager.RINGER_MODE_NORMAL),
+ RingerMode(AudioManager.RINGER_MODE_SILENT),
+ )
+ }
+ }
+
+ @Test
+ fun communicationDeviceChanges_repositoryEmits() {
+ testScope.runTest {
+ var device: AudioDeviceInfo? = null
+ underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerConnectedDeviceChange(communicationDevice)
+ runCurrent()
+
+ assertThat(device).isSameInstanceAs(communicationDevice)
+ }
+ }
+
+ @Test
+ fun adjustingVolume_changesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 50,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustingVolume_currentModeIsUpToDate() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+ }
+ }
+
+ @Test
+ fun muteStream_mutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun unmuteStream_unmutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ isMuteByStream[audioStream.value] = true
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+ verify(audioManager)
+ .addOnCommunicationDeviceChangedListener(
+ any(),
+ communicationDeviceListenerCaptor.capture(),
+ )
+ communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+ }
+
+ private fun triggerModeChange(mode: Int) {
+ verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+ modeListenerCaptor.value.onModeChanged(mode)
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ const val MIN_VOLUME = 0
+ const val MAX_VOLUME = 100
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362fadf02..dddf8e82d5f7 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
package com.android.settingslib.volume.data.repository
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
@@ -26,7 +32,59 @@ class FakeAudioRepository : AudioRepository {
override val mode: StateFlow<Int>
get() = mutableMode.asStateFlow()
+ private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ override val ringerMode: StateFlow<RingerMode>
+ get() = mutableRingerMode.asStateFlow()
+
+ private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() = mutableCommunicationDevice.asStateFlow()
+
+ private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+ private fun getAudioStreamModelState(
+ audioStream: AudioStream
+ ): MutableStateFlow<AudioStreamModel> =
+ models.getOrPut(audioStream) {
+ MutableStateFlow(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = 0,
+ maxVolume = 0,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ getAudioStreamModelState(audioStream).asStateFlow()
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+ getAudioStreamModelState(audioStream).value
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ }
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
+
+ fun setRingerMode(newRingerMode: RingerMode) {
+ mutableRingerMode.value = newRingerMode
+ }
+
+ fun setCommunicationDevice(device: AudioDeviceInfo?) {
+ mutableCommunicationDevice.value = device
+ }
+
+ fun setAudioStreamModel(model: AudioStreamModel) {
+ getAudioStreamModelState(model.audioStream).update { model }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc9c944..4dbf865475a4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
package com.android.settingslib.volume.domain.interactor
import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
import com.android.settingslib.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index ceba9be70487..e2d58d660fd5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -24,6 +24,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.getMediaTransferThisDeviceName;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +115,7 @@ public class PhoneMediaDeviceTest {
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
+ .isEqualTo(getMediaTransferThisDeviceName(mContext));
}
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index 6d6e2ff8e59b..e2eda4f2691f 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,5 +32,7 @@ android_test {
"androidx.test.ext.junit",
"androidx.test.runner",
"truth",
+ "kotlinx_coroutines_test",
+ "mockito-target-minus-junit4",
],
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b0fa09..e99fcc92dea7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
<!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
<uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+ <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+ <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+ <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3db99f284e4c..ba3026ec18f8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,16 @@ flag {
}
flag {
+ name: "nssl_falsing_fix"
+ namespace: "systemui"
+ description: "Minor touch changes to prevent falsing errors in NSSL"
+ bug: "316551193"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "refactor_get_current_user"
namespace: "systemui"
description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
@@ -94,7 +104,7 @@ flag {
" standard background color is desired. This was the behavior before we discovered"
" a resources threading issue, which we worked around by tinting the notification"
" backgrounds and footer buttons."
- bug: "294347738"
+ bug: "294830092"
}
flag {
@@ -323,13 +333,6 @@ flag {
}
flag {
- name: "screenshare_notification_hiding"
- namespace: "systemui"
- description: "Enable hiding of notifications during screenshare"
- bug: "312784809"
-}
-
-flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -365,9 +368,9 @@ flag {
}
flag {
- name: "enable_keyguard_compose"
+ name: "compose_lockscreen"
namespace: "systemui"
- description: "Enables the compose version of keyguard."
+ description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
bug: "301968149"
}
@@ -384,3 +387,11 @@ flag {
description: "Enables on-screen contextual tip about how to take screenshot."
bug: "322891421"
}
+
+flag {
+ name: "shaderlib_loading_effect_refactor"
+ namespace: "systemui"
+ description: "Extend shader library to provide the common loading effects."
+ bug: "282007590"
+}
+
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 576596fa67fe..4e72dfef82e1 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
@@ -160,14 +160,23 @@ fun CommunalHub(
gridCoordinates
) {
detectLongPressGesture { offset ->
- isButtonToEditWidgetsShowing = true
-
// Deduct both grid offset relative to its container and content offset.
val adjustedOffset =
gridCoordinates?.let {
offset - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidget() ||
+ communalContent[index] is CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
+ }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
viewModel.setSelectedKey(key)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 68e57b5d51b8..071433eba98d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -53,7 +53,7 @@ fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier
stickyKeys.forEach { (key, isLocked) ->
key(key) {
Text(
- text = key.text,
+ text = key.displayedText,
fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5258078e0e9a..d904c8b770bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -504,6 +504,7 @@ internal fun SceneTransitionLayoutForTesting(
layoutImpl.density = density
layoutImpl.swipeSourceDetector = swipeSourceDetector
+ layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0eca0d8b..2dc94a405ae3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@ import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +49,7 @@ private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
private class TestGestureScope(
- val coroutineScope: MonotonicClockTestScope,
+ private val testScope: MonotonicClockTestScope,
) {
private val layoutState =
MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@ class SceneGestureHandlerTest {
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
- coroutineScope = coroutineScope,
+ coroutineScope = testScope,
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
- val draggable = sceneGestureHandler.draggable
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
SceneNestedScrollHandler(
- layoutImpl,
+ layoutImpl = layoutImpl,
orientation = sceneGestureHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@ class SceneGestureHandlerTest {
fun up(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
- // Offset y: 10% of the screen
- val offsetY10 = Offset(x = 0f, y = down(0.1f))
+ fun downOffset(fractionOfScreen: Float) =
+ if (fractionOfScreen < 0f) {
+ error("upOffset() is required, not implemented yet")
+ } else {
+ Offset(x = 0f, y = down(fractionOfScreen))
+ }
val transitionState: TransitionState
get() = layoutState.transitionState
@@ -127,15 +132,15 @@ class SceneGestureHandlerTest {
get() = (transitionState as Transition).progress
fun advanceUntilIdle() {
- coroutineScope.testScheduler.advanceUntilIdle()
+ testScope.testScheduler.advanceUntilIdle()
}
fun runCurrent() {
- coroutineScope.testScheduler.runCurrent()
+ testScope.testScheduler.runCurrent()
}
fun assertIdle(currentScene: SceneKey) {
- assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+ assertThat(transitionState).isInstanceOf(Idle::class.java)
assertWithMessage("currentScene does not match")
.that(transitionState.currentScene)
.isEqualTo(currentScene)
@@ -148,11 +153,8 @@ class SceneGestureHandlerTest {
progress: Float? = null,
isUserInputOngoing: Boolean? = null
) {
- val transition = transitionState
- assertWithMessage("transitionState must be Transition")
- .that(transition is Transition)
- .isTrue()
- transition as Transition
+ assertThat(transitionState).isInstanceOf(Transition::class.java)
+ val transition = transitionState as Transition
if (currentScene != null)
assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@ class SceneGestureHandlerTest {
.that(transition.isUserInputOngoing)
.isEqualTo(isUserInputOngoing)
}
+
+ fun onDragStarted(
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float,
+ pointersDown: Int = 1
+ ) {
+ // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+ if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+ onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ }
+
+ fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+ onDragStarted(
+ sceneGestureHandler.draggable,
+ startedPosition,
+ overSlop = 0f,
+ pointersDown
+ )
+ }
+
+ fun onDragStarted(
+ draggableHandler: DraggableHandler,
+ startedPosition: Offset = Offset.Zero,
+ overSlop: Float = 0f,
+ pointersDown: Int = 1
+ ) {
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
+
+ // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+ onDelta(pixels = overSlop)
+ }
+
+ fun onDelta(pixels: Float) {
+ sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ }
+
+ fun onDragStopped(velocity: Float) {
+ sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+ runCurrent()
+ }
+
+ fun NestedScrollConnection.scroll(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ onPreScroll(
+ available = available,
+ source = NestedScrollSource.Drag,
+ )
+ val consumed = consumedByPreScroll + consumedByScroll
+
+ onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ fun NestedScrollConnection.preFling(
+ available: Velocity,
+ coroutineScope: CoroutineScope = testScope,
+ ) {
+ // onPreFling is a suspend function that returns the consumed velocity once it finishes
+ // consuming it. In the current scenario, it returns after completing the animation.
+ // To return immediately, we can initiate a job that allows us to check the status
+ // before the animation starts.
+ coroutineScope.launch { onPreFling(available = available) }
+ runCurrent()
+ }
}
private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
- runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
- }
+ runMonotonicClockTest {
+ val testGestureScope = TestGestureScope(testScope = this)
- private fun DraggableHandler.onDragStarted(
- overSlop: Float = 0f,
- startedPosition: Offset = Offset.Zero,
- ) {
- onDragStarted(startedPosition, overSlop)
- // MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(overSlop)
+ // run the test
+ testGestureScope.block()
+ }
}
@Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- draggable.onDelta(pixels = down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold - 0.01f,
- )
+ onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -230,11 +300,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = velocityThreshold)
-
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -244,10 +313,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(velocity = 0f)
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -255,7 +324,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted(overSlop = -60f)
+ onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -264,7 +333,7 @@ class SceneGestureHandlerTest {
)
// Reverse direction such that A -> C now with 0.4
- draggable.onDelta(100f)
+ onDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -273,7 +342,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -283,9 +352,12 @@ class SceneGestureHandlerTest {
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+ val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+ onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
- horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+ onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
}
@@ -294,7 +366,7 @@ class SceneGestureHandlerTest {
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- draggable.onDragStarted(10f)
+ onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -303,7 +375,7 @@ class SceneGestureHandlerTest {
)
// Reverse drag direction, it will consume the previous drag
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -312,7 +384,7 @@ class SceneGestureHandlerTest {
)
// Continue reverse drag direction, it should record progress to Scene B
- draggable.onDelta(-10f)
+ onDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -326,7 +398,10 @@ class SceneGestureHandlerTest {
navigateToSceneC()
// Start dragging from the bottom
- draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+ onDragStarted(
+ startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ overSlop = up(fractionOfScreen = 0.1f)
+ )
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -337,14 +412,14 @@ class SceneGestureHandlerTest {
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- draggable.onDragStarted(down(0.3f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- draggable.onDelta(up(0.3f))
+ onDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -355,8 +430,8 @@ class SceneGestureHandlerTest {
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- draggable.onDragStarted(down(1f))
- draggable.onDragStopped(0f)
+ onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -364,7 +439,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted(overSlop = up(0.2f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -373,13 +448,13 @@ class SceneGestureHandlerTest {
)
// Start animation A -> B with progress 0.2 -> 1.0
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -388,7 +463,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -398,9 +473,9 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted(overSlop = up(0.2f))
- draggable.onDelta(up(0.2f))
- draggable.onDragStopped(velocity = -velocityThreshold)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ onDelta(pixels = up(fractionOfScreen = 0.2f))
+ onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@ class SceneGestureHandlerTest {
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- draggable.onDragStarted()
- draggable.onDelta(up(0.5f))
- draggable.onDelta(up(0.5f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- draggable.onDelta(up(0.5f))
- draggable.onDragStopped(0f)
+ onDelta(pixels = up(fractionOfScreen = 0.5f))
+ onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- draggable.onDragStopped(down(0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- draggable.onDragStarted(up(0.1f))
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = SceneC
- draggable.onDelta(up(0.1f))
- draggable.onDragStopped(down(0.1f))
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
+ onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(overSlop = 0f)
- draggable.onDelta(up(0.1f))
+ onDragStartedImmediately()
+ onDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(overSlop = down(0.1f))
+ onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- draggable.onDragStopped(
- velocity = velocityThreshold,
- )
-
- // The stop animation is not started yet
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
- runCurrent()
+ onDragStopped(velocity = velocityThreshold)
- assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
- assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
assertTransition(currentScene = SceneC)
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
// Start a new gesture while the offset is animating
- draggable.onDragStarted()
+ onDragStartedImmediately()
assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
}
@Test
fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertIdle(currentScene = SceneA)
}
@@ -509,40 +580,29 @@ class SceneGestureHandlerTest {
val consumed =
nestedScroll.onPostScroll(
consumed = Offset.Zero,
- available = offsetY10,
+ available = downOffset(fractionOfScreen = 0.1f),
source = NestedScrollSource.Drag
)
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- assertThat(consumed).isEqualTo(offsetY10)
- }
-
- private fun NestedScrollConnection.scroll(
- available: Offset,
- consumedByScroll: Offset = Offset.Zero,
- ) {
- val consumedByPreScroll =
- onPreScroll(available = available, source = NestedScrollSource.Drag)
- val consumed = consumedByPreScroll + consumedByScroll
- onPostScroll(
- consumed = consumed,
- available = available - consumed,
- source = NestedScrollSource.Drag
- )
+ assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
}
@Test
fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
// start intercept preScroll
val consumed =
- nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ source = NestedScrollSource.Drag
+ )
assertThat(progress).isEqualTo(0.2f)
// do nothing on postScroll
@@ -553,12 +613,12 @@ class SceneGestureHandlerTest {
)
assertThat(progress).isEqualTo(0.2f)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.3f)
assertTransition(currentScene = SceneA)
}
- private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+ private fun TestGestureScope.preScrollAfterSceneTransition(
firstScroll: Float,
secondScroll: Float
) {
@@ -567,10 +627,13 @@ class SceneGestureHandlerTest {
nestedScroll.scroll(available = Offset(0f, firstScroll))
// stop scene transition (start the "stop animation")
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
// a pre scroll event, that could be intercepted by SceneGestureHandler
- nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+ nestedScroll.onPreScroll(
+ available = Offset(0f, secondScroll),
+ source = NestedScrollSource.Drag
+ )
}
@Test
@@ -610,16 +673,17 @@ class SceneGestureHandlerTest {
preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
+ advanceUntilIdle()
assertIdle(SceneB)
}
@Test
fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- nestedScroll.onPreFling(available = Velocity.Zero)
+ nestedScroll.preFling(available = Velocity.Zero)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -627,15 +691,15 @@ class SceneGestureHandlerTest {
assertIdle(currentScene = SceneA)
}
- private suspend fun TestGestureScope.flingAfterScroll(
+ private fun TestGestureScope.flingAfterScroll(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -679,19 +743,22 @@ class SceneGestureHandlerTest {
}
/** we started the scroll in the scene, then fling with the velocityThreshold */
- private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+ private fun TestGestureScope.flingAfterScrollStartedInScene(
use: NestedScrollBehavior,
idleAfterScroll: Boolean,
) {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
// scroll consumed in child
- nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+ nestedScroll.scroll(
+ available = downOffset(fractionOfScreen = 0.1f),
+ consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+ )
// scroll offsetY10 is all available for parents
- nestedScroll.scroll(available = offsetY10)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
- nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
}
@Test
@@ -732,20 +799,20 @@ class SceneGestureHandlerTest {
@Test
fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- draggable.onDelta(down(0.1f))
+ onDelta(pixels = down(fractionOfScreen = 0.1f))
assertIdle(currentScene = SceneA)
}
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- draggable.onDragStopped(velocityThreshold)
+ onDragStopped(velocity = velocityThreshold)
assertIdle(currentScene = SceneA)
}
@Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
- nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
assertIdle(currentScene = SceneA)
}
@@ -753,8 +820,10 @@ class SceneGestureHandlerTest {
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
// Start a drag and then stop it, given that
- draggable.onDragStarted(overSlop = up(0.1f))
+ onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@ class SceneGestureHandlerTest {
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- draggable.onDragStopped(-velocityThreshold)
+ onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@ class SceneGestureHandlerTest {
nestedScroll.scroll(available = -offsetY10)
assertThat(progress).isEqualTo(0.4f)
- nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+ nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
// wait for the stop animation
@@ -788,7 +857,7 @@ class SceneGestureHandlerTest {
// Swipe up from the middle to transition to scene B.
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -802,7 +871,7 @@ class SceneGestureHandlerTest {
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
- draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+ onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@ class SceneGestureHandlerTest {
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b27836e..0245cf2a26b8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@ package com.android.compose.test
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
@@ -12,16 +14,38 @@ import kotlinx.coroutines.withContext
* function.
*
* The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
- // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
- withContext(TestMonotonicFrameClock(this)) {
- MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ val testScope: TestScope = this
+
+ withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+ val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+ val scope =
+ MonotonicClockTestScope(
+ testScope = testScopeWithMonotonicFrameClock,
+ testScheduler = testScope.testScheduler,
+ backgroundScope = backgroundScope,
+ )
+
+ // Run the test
+ scope.block()
}
}
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ * in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
class MonotonicClockTestScope(
- coroutineScope: CoroutineScope,
- val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+ testScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler,
+ val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105a20a0..e39d7edddcfd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@ import android.provider.Settings
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@ class NotificationSettingsRepository(
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
+ val isNotificationHistoryEnabled: Flow<Boolean> =
+ secureSettingsRepository
+ .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+ .map { it == 1 }
+ .distinctUntilChanged()
+
/** The current state of the notification setting. */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8d3811..04e8090e3ae2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@ import kotlinx.coroutines.flow.StateFlow
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
+ val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
/** Should notifications be visible on the lockscreen? */
val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
repository.isShowNotificationsOnLockScreenEnabled
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index b6605ed9d007..fa47a02d78c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -59,15 +59,21 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(actualValue).isTrue()
@@ -78,25 +84,13 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
scope.runTest {
val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(flowValues.size).isEqualTo(3)
@@ -109,26 +103,14 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser1.identifier
- )
- settings.putIntForUser(
- SETTING_NAME,
- DISABLED,
- testUser2.identifier
- )
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isFalse()
Truth.assertThat(lastValueUser2).isFalse()
- settings.putIntForUser(
- SETTING_NAME,
- ENABLED,
- testUser1.identifier
- )
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
Truth.assertThat(lastValueUser1).isTrue()
@@ -142,11 +124,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@@ -157,11 +135,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
runCurrent()
Truth.assertThat(success).isTrue()
- val actualValue =
- settings.getIntForUser(
- SETTING_NAME,
- testUser1.identifier
- )
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 30eb782e5938..3d8159e70061 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -59,6 +60,16 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ Truth.assertThat(actualValue).isFalse()
+ }
+
+ @Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
val actualValue by collectLastValue(underTest.isEnabled(testUser1))
@@ -72,8 +83,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- val flowValues: List<Boolean> by
- collectValues(underTest.isEnabled(testUser1))
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 76b0d4aaa8ca..45f98be2ca12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -20,6 +20,7 @@ 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.log.table.TableLogBuffer
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -44,78 +45,79 @@ import org.mockito.MockitoAnnotations
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var mediaData: MediaData
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private lateinit var underTest: CommunalMediaRepositoryImpl
private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
}
- private lateinit var mediaRepository: CommunalMediaRepository
-
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CommunalMediaRepositoryImpl(
+ mediaDataManager,
+ tableLogBuffer,
+ )
}
@Test
fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
@Test
fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Initial value is false.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ val mediaModel = collectLastValue(underTest.mediaModel)
runCurrent()
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
}
@Test
fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Change to media available and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
// Media active now returns true.
- val mediaModel = collectLastValue(mediaRepository.mediaModel)
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ val mediaModel = collectLastValue(underTest.mediaModel)
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
// Media active now returns false.
- assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+ assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index d15e15e179fb..6bff0dc7bd9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -25,6 +25,8 @@ import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.settings.UserFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -36,11 +38,15 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private lateinit var underTest: CommunalPrefsRepositoryImpl
private val kosmos = testKosmos()
@@ -51,6 +57,8 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
userRepository = kosmos.fakeUserRepository
userRepository.setUserInfos(USER_INFOS)
@@ -67,6 +75,8 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
kosmos.testDispatcher,
userRepository,
userFileManager,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index 0c66bbb63439..2911a50c2737 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
@@ -34,12 +35,15 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -64,6 +68,7 @@ class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
userRepository,
secureSettings,
logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ tableLogBuffer,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c979ca63950a..475179dfdb72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -198,13 +198,22 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ fun deleteWidgetFromDb() =
testScope.runTest {
val id = 1
- underTest.deleteWidget(id)
+ underTest.deleteWidgetFromDb(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
+ }
+
+ @Test
+ fun deleteWidgetFromHost() =
+ testScope.runTest {
+ val id = 1
+ underTest.deleteWidgetFromHost(id)
+ runCurrent()
+
verify(appWidgetHost).deleteAppWidgetId(id)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index ee01bf9c26e4..c5485c5c5c33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -642,6 +642,53 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
+ fun isCommunalVisible() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+ )
+ communalRepository.setTransitionState(transitionState)
+
+ // isCommunalVisible is false when not on communal.
+ val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
+ assertThat(isCommunalVisible).isEqualTo(false)
+
+ // Start transition to communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Blank,
+ toScene = CommunalSceneKey.Communal,
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is true once transition starts.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value =
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+
+ // isCommunalVisible is true since we're on communal.
+ assertThat(isCommunalVisible).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableCommunalTransitionState.Transition(
+ fromScene = CommunalSceneKey.Communal,
+ toScene = CommunalSceneKey.Blank,
+ progress = flowOf(1.0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ // isCommunalVisible is still true as the false as soon as transition away runs.
+ assertThat(isCommunalVisible).isEqualTo(true)
+ }
+
+ @Test
fun testShowWidgetEditorStartsActivity() =
testScope.runTest {
underTest.showWidgetEditor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 273d1cd55626..cf727cf5e180 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
@@ -82,6 +83,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
kosmos.communalInteractor,
mediaHost,
uiEventLogger,
+ logcatLogBuffer("CommunalEditModeViewModelTest"),
)
}
@@ -142,6 +144,49 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
+ fun deleteWidget() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Widgets available.
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = mock(),
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = mock(),
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+
+ underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+
+ // Only one widget and CTA tile remain.
+ assertThat(communalContent?.size).isEqualTo(2)
+ val item = communalContent?.get(0)
+ val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+ assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
+ }
+
+ @Test
fun reorderWidget_uiEventLogging_start() {
underTest.onReorderWidgetStart()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
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 0723e8306f71..73d309118548 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
@@ -40,6 +40,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
@@ -99,6 +100,7 @@ class CommunalViewModelTest : SysuiTestCase() {
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
mediaHost,
+ logcatLogBuffer("CommunalViewModelTest"),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index efccf7a81ccd..e4ce6cb992c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -17,7 +17,7 @@ package com.android.systemui.dreams.homecontrols
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.Kosmos
@@ -38,4 +38,3 @@ val Kosmos.homeControlsComponentInteractor by
val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
-val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index ce74a905ef66..6ad32cc39f23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -27,13 +27,14 @@ import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -41,6 +42,9 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -48,6 +52,7 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class HomeControlsComponentInteractorTest : SysuiTestCase() {
@@ -59,20 +64,20 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
private lateinit var underTest: HomeControlsComponentInteractor
private lateinit var userRepository: FakeUserRepository
- private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+ private lateinit var selectedComponentRepository: SelectedComponentRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- userRepository = kosmos.fakeUserRepository
- userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
controlsComponent = kosmos.controlsComponent
authorizedPanelsRepository = kosmos.authorizedPanelsRepository
controlsListingController = kosmos.controlsListingController
selectedComponentRepository = kosmos.selectedComponentRepository
- selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -90,14 +95,13 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -105,16 +109,15 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
whenever(controlsListingController.getCurrentServices())
.thenReturn(
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
)
val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
}
}
@@ -122,9 +125,8 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -137,8 +139,8 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
- userRepository.setSelectedUserInfo(PRIMARY_USER)
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
val actualValue by collectLastValue(underTest.panelComponent)
assertThat(actualValue).isNull()
@@ -151,17 +153,24 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsComponentNameForDifferentUsers() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
- userRepository.setSelectedUserInfo(ANOTHER_USER)
+ val actualValue by collectLastValue(underTest.panelComponent)
+
+ // Secondary user has non-panel selected.
+ setActiveUser(ANOTHER_USER)
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
- selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+
+ // Primary user has panel selected.
+ setActiveUser(PRIMARY_USER)
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
- val actualValue by collectLastValue(underTest.panelComponent)
- assertThat(actualValue).isNull()
runServicesUpdate()
- assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+ assertThat(actualValue).isEqualTo(TEST_COMPONENT)
+
+ // Back to secondary user, should be null.
+ setActiveUser(ANOTHER_USER)
+ runServicesUpdate()
+ assertThat(actualValue).isNull()
}
}
@@ -169,8 +178,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
with(kosmos) {
testScope.runTest {
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.empty())
userRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -182,11 +190,17 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
val listings =
- listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+ listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
callback.onServicesUpdated(listings)
}
+ private suspend fun TestScope.setActiveUser(user: UserInfo) {
+ userRepository.setSelectedUserInfo(user)
+ kosmos.fakeUserTracker.set(listOf(user), 0)
+ runCurrent()
+ }
+
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
@@ -237,19 +251,9 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
)
private const val TEST_PACKAGE = "pkg"
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
- private const val TEST_PACKAGE_PANEL = "pkg.panel"
- private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
private val TEST_SELECTED_COMPONENT_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- true
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
private val TEST_SELECTED_COMPONENT_NON_PANEL =
- SelectedComponentRepository.SelectedComponent(
- TEST_PACKAGE_PANEL,
- TEST_COMPONENT_PANEL,
- false
- )
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 6610e7007eb2..87b1bbb9ff70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,8 +85,7 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
- whenever(authorizedPanelsRepository.getAuthorizedPanels())
- .thenReturn(setOf(TEST_PACKAGE_PANEL))
+ authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 74c197075461..2a9bc4a1d80e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
@@ -47,8 +46,6 @@ public class CommunalTouchHandlerTest extends SysuiTestCase {
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock
DreamTouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@@ -59,17 +56,10 @@ public class CommunalTouchHandlerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
- mNotificationShadeWindowController,
INITIATION_WIDTH);
}
@Test
- public void testSessionStartForcesShadeOpen() {
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
- }
-
- @Test
public void testEventPropagation() {
final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
index 2fe4ef78bfdc..f400cb18295c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -25,9 +25,11 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
@@ -146,6 +148,7 @@ class SideFpsProgressBarViewModelTest : SysuiTestCase() {
kosmos.fakeKeyguardRepository.setIsDozing(false)
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.DeviceEntryAuthentication,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
)
)
@@ -165,6 +168,7 @@ class SideFpsProgressBarViewModelTest : SysuiTestCase() {
kosmos.fakeKeyguardRepository.setIsDozing(true)
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.DeviceEntryAuthentication,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
)
)
@@ -177,6 +181,7 @@ class SideFpsProgressBarViewModelTest : SysuiTestCase() {
private fun createViewModel() =
SideFpsProgressBarViewModel(
kosmos.applicationContext,
+ kosmos.biometricStatusInteractor,
kosmos.deviceEntryFingerprintAuthInteractor,
kosmos.sideFpsSensorInteractor,
kosmos.dozeServiceHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
new file mode 100644
index 000000000000..b7b3fdbed955
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.fontscaling.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.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+ private val mapper by lazy {
+ FontScalingTileMapper(
+ context.orCreateTestableResources
+ .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun activeStateMatchesEnabledModel() {
+ val inputModel = FontScalingTileModel
+
+ val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+ val expectedState = createFontScalingTileState()
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createFontScalingTileState(): QSTileState =
+ QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ )!!,
+ null
+ )
+ },
+ context.getString(R.string.quick_settings_font_scaling_label),
+ QSTileState.ActivationState.ACTIVE,
+ null,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ context.getString(R.string.quick_settings_font_scaling_label),
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 000000000000..39bc8a6fa6a2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+ private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+ private val testUser = UserHandle.of(1)
+
+ @Test
+ fun collectsExactlyOneValue() = runTest {
+ val flowValues by
+ collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValues.size).isEqualTo(1)
+ }
+
+ @Test
+ fun lastValueIsNotEmpty() = runTest {
+ val flowValue by
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+
+ Truth.assertThat(flowValue).isNotNull()
+ }
+
+ @Test
+ fun isAvailable() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ Truth.assertThat(availability).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 000000000000..2384915c8703
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.fontscaling.domain.interactor
+
+import android.provider.Settings
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+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.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardStateController = FakeKeyguardStateController()
+
+ private lateinit var underTest: FontScalingTileUserActionInteractor
+
+ @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+ @Before
+ fun setup() {
+ activityStarter = mock<ActivityStarter>()
+ dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+ dialog = mock<SystemUIDialog>()
+ fontScalingDialogDelegate =
+ mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+ argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+ underTest =
+ FontScalingTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ qsTileIntentUserActionHandler,
+ { fontScalingDialogDelegate },
+ keyguardStateController,
+ dialogLaunchAnimator,
+ activityStarter
+ )
+ }
+
+ @Test
+ fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = false
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+ }
+
+ @Test
+ fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+ kosmos.testScope.runTest {
+ keyguardStateController.isShowing = true
+ val testView = View(context)
+
+ underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+ verify(activityStarter)
+ .executeRunnableDismissingKeyguard(
+ argumentCaptor.capture(),
+ eq(null),
+ eq(true),
+ eq(true),
+ eq(false)
+ )
+ argumentCaptor.value.run()
+ verify(dialogLaunchAnimator, never())
+ .showFromView(any(), eq(testView), nullable(), anyBoolean())
+ verify(dialog).show()
+ }
+
+ @Test
+ fun handleLongClick() =
+ kosmos.testScope.runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+ Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+ Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 504ded30a06c..189ba7b1965a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -76,6 +76,8 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -262,6 +264,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
startable.start()
@@ -518,6 +521,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
assertCurrentScene(SceneKey.Lockscreen)
}
+ @Test
+ fun deviceProvisioningAndFactoryResetProtection() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(false)
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+ assertThat(isVisible).isTrue()
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16cb6231a872..12dbf11255b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,8 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -112,6 +114,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
+ deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
)
}
@@ -162,6 +165,30 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun hydrateVisibility_basedOnDeviceProvisioningAndFactoryResetProtection() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ isDeviceProvisioned = false,
+ isFrpActive = true,
+ )
+
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ assertThat(isVisible).isFalse()
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+ assertThat(isVisible).isTrue()
+
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
fun startsInLockscreenScene() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
@@ -743,6 +770,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
authenticationMethod: AuthenticationMethodModel? = null,
isLockscreenEnabled: Boolean = true,
startsAwake: Boolean = true,
+ isDeviceProvisioned: Boolean = true,
+ isFrpActive: Boolean = false,
): MutableStateFlow<ObservableTransitionState> {
if (authenticationMethod?.isSecure == true) {
assert(isLockscreenEnabled) {
@@ -779,6 +808,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
} else {
powerInteractor.setAsleepForTest()
}
+
+ kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
+ kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isFrpActive)
+
runCurrent()
return transitionStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f6fb1c..1f6ba29c5fc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@ public class FakeKeyguardStateController implements KeyguardStateController {
mOccluded = occluded;
}
+ public void setShowing(boolean isShowing) {
+ mShowing = isShowing;
+ }
+
@Override
public boolean isShowing() {
return mShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index c7e5b645db5f..c23a05128df5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -61,7 +61,8 @@ constructor(
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index 419eada91f87..852579794a27 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -61,7 +61,8 @@ constructor(
.observerFlow(userHandle.identifier, SETTING_NAME)
.onStart { emit(Unit) }
.map {
- secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
}
.distinctUntilChanged()
.flowOn(bgCoroutineContext)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35a4681..4047623a4b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@ import com.android.systemui.qs.tiles.impl.colorcorrection.domain.ColorCorrection
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@ interface QSAccessibilityModule {
companion object {
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
+ const val FONT_SCALING_TILE_SPEC = "font_scaling"
@Provides
@IntoMap
@@ -155,5 +160,36 @@ interface QSAccessibilityModule {
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_font_scaling,
+ labelRes = R.string.quick_settings_font_scaling_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(FONT_SCALING_TILE_SPEC)
+ fun provideFontScalingTileViewModel(
+ factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+ mapper: FontScalingTileMapper,
+ stateInteractor: FontScalingTileDataInteractor,
+ userActionInteractor: FontScalingTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(FONT_SCALING_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 417608336974..4ea5f4c7af0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
else ->
UdfpsTouchOverlayBinder.bind(
view = this,
viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index d28dbc0ae06f..27bb023e3366 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -24,17 +24,24 @@ import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.biometrics.shared.model.AuthenticationState
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
/** A repository for the state of biometric authentication. */
@@ -44,6 +51,9 @@ interface BiometricStatusRepository {
* [NotRunning].
*/
val fingerprintAuthenticationReason: Flow<AuthenticationReason>
+
+ /** The current status of an acquired fingerprint. */
+ val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
}
@SysUISingleton
@@ -54,53 +64,53 @@ constructor(
private val biometricManager: BiometricManager?
) : BiometricStatusRepository {
- override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ private val authenticationState: Flow<AuthenticationState> =
conflatedCallbackFlow {
- val updateFingerprintAuthenticateReason = { reason: AuthenticationReason ->
- trySendWithFailureLogging(
- reason,
- TAG,
- "Error sending fingerprintAuthenticateReason reason"
- )
+ val updateAuthenticationState = { state: AuthenticationState ->
+ trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
}
val authenticationStateListener =
object : AuthenticationStateListener.Stub() {
override fun onAuthenticationStarted(requestReason: Int) {
- val authenticationReason =
- when (requestReason) {
- REASON_AUTH_BP ->
- AuthenticationReason.BiometricPromptAuthentication
- REASON_AUTH_KEYGUARD ->
- AuthenticationReason.DeviceEntryAuthentication
- REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
- REASON_AUTH_SETTINGS ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.OTHER
- )
- REASON_ENROLL_ENROLLING ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.ENROLL_ENROLLING
- )
- REASON_ENROLL_FIND_SENSOR ->
- AuthenticationReason.SettingsAuthentication(
- SettingsOperations.ENROLL_FIND_SENSOR
- )
- else -> AuthenticationReason.Unknown
- }
- updateFingerprintAuthenticateReason(authenticationReason)
+ val authenticationReason = requestReason.toAuthenticationReason()
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStarted(authenticationReason)
+ )
}
override fun onAuthenticationStopped() {
- updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStopped(
+ AuthenticationReason.NotRunning
+ )
+ )
}
override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
+
+ override fun onAuthenticationAcquired(
+ biometricSourceType: BiometricSourceType,
+ requestReason: Int,
+ acquiredInfo: Int
+ ) {
+ val authReason = requestReason.toAuthenticationReason()
+
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationAcquired(
+ biometricSourceType,
+ authReason,
+ acquiredInfo
+ )
+ )
+ }
}
- updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+ updateAuthenticationState(
+ AuthenticationState.AuthenticationStarted(AuthenticationReason.NotRunning)
+ )
biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
awaitClose {
biometricManager?.unregisterAuthenticationStateListener(
@@ -110,7 +120,36 @@ constructor(
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+ override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ authenticationState.map { it.requestReason }
+
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ authenticationState
+ .filterIsInstance<AuthenticationState.AuthenticationAcquired>()
+ .filter {
+ it.biometricSourceType == BiometricSourceType.FINGERPRINT &&
+ // TODO(b/322555228) This check will be removed after consolidating device
+ // entry auth messages (currently in DeviceEntryFingerprintAuthRepository)
+ // with BP auth messages (here)
+ it.requestReason == AuthenticationReason.BiometricPromptAuthentication
+ }
+ .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+
companion object {
private const val TAG = "BiometricStatusRepositoryImpl"
}
}
+
+private fun Int.toAuthenticationReason(): AuthenticationReason =
+ when (this) {
+ REASON_AUTH_BP -> AuthenticationReason.BiometricPromptAuthentication
+ REASON_AUTH_KEYGUARD -> AuthenticationReason.DeviceEntryAuthentication
+ REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
+ REASON_AUTH_SETTINGS ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)
+ REASON_ENROLL_ENROLLING ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+ REASON_ENROLL_FIND_SENSOR ->
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR)
+ else -> AuthenticationReason.Unknown
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 55a2d3d7563e..ed1557cccd01 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -20,6 +20,7 @@ import android.app.ActivityTaskManager
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -31,6 +32,9 @@ interface BiometricStatusInteractor {
* filtered for when the overlay should be shown, otherwise [NotRunning].
*/
val sfpsAuthenticationReason: Flow<AuthenticationReason>
+
+ /** The current status of an acquired fingerprint. */
+ val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
}
class BiometricStatusInteractorImpl
@@ -50,6 +54,9 @@ constructor(
}
}
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ biometricStatusRepository.fingerprintAcquiredStatus
+
companion object {
private const val TAG = "BiometricStatusInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
new file mode 100644
index 000000000000..77cf8406725f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.biometrics.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/**
+ * Describes the current state of biometric authentication, including whether authentication is
+ * started, stopped, or acquired and relevant parameters, and the [AuthenticationReason] for
+ * authentication.
+ */
+sealed interface AuthenticationState {
+ val requestReason: AuthenticationReason
+
+ /**
+ * Authentication started
+ *
+ * @param requestReason [AuthenticationReason] for starting authentication
+ */
+ data class AuthenticationStarted(override val requestReason: AuthenticationReason) :
+ AuthenticationState
+
+ /**
+ * Authentication stopped
+ *
+ * @param requestReason [AuthenticationReason.NotRunning]
+ */
+ data class AuthenticationStopped(override val requestReason: AuthenticationReason) :
+ AuthenticationState
+
+ /**
+ * Authentication acquired
+ *
+ * @param biometricSourceType indicates [BiometricSourceType] of acquired authentication
+ * @param requestReason indicates [AuthenticationReason] for requesting auth
+ * @param acquiredInfo indicates
+ */
+ data class AuthenticationAcquired(
+ val biometricSourceType: BiometricSourceType,
+ override val requestReason: AuthenticationReason,
+ val acquiredInfo: Int
+ ) : AuthenticationState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 80d37b4741e4..7b4be0220ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -50,10 +50,12 @@ import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class SideFpsOverlayViewBinder
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b7aa2c..2e29c3b59c4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics.ui.binder
+import android.util.Log
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@ object UdfpsTouchOverlayBinder {
/**
* Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
- * touches or not.
+ * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+ * viewBinder will send the information to the interactor.
*/
@JvmStatic
fun bind(
view: UdfpsTouchOverlay,
viewModel: UdfpsTouchOverlayViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+ Log.d(
+ "UdfpsTouchOverlayBinder",
+ "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+ )
view.isInvisible = !shouldHandleTouches
+ udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0f1340a63032..a39aabfabd73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
@@ -244,7 +245,13 @@ constructor(
!customBiometricPrompt() || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ else ->
+ try {
+ context.packageManager.getApplicationIcon(it.opPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
+ null
+ }
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index ce726034913f..cfda75c7851a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -41,12 +41,14 @@ import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlay
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
/** Models UI of the side fingerprint sensor indicator view. */
+@OptIn(ExperimentalCoroutinesApi::class)
class SideFpsOverlayViewModel
@Inject
constructor(
@@ -176,8 +178,8 @@ constructor(
val lottieCallbacks: Flow<List<LottieCallback>> =
combine(
biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
- sideFpsProgressBarViewModel.isVisible,
+ deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.isVisible
) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
->
val callbacks = mutableListOf<LottieCallback>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 779446d3cd37..3b727d2a7ade 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@ interface CommunalWidgetDao {
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
- fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+ fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
@@ -120,7 +120,9 @@ interface CommunalWidgetDao {
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
widgetIdToPriorityMap.forEach { (id, priority) ->
val widget = getWidgetByIdNow(id)
- updateItemRank(widget.itemId, priority)
+ if (widget != null) {
+ updateItemRank(widget.itemId, priority)
+ }
}
}
@@ -134,9 +136,13 @@ interface CommunalWidgetDao {
}
@Transaction
- fun deleteWidgetById(widgetId: Int) {
- val widget = getWidgetByIdNow(widgetId)
+ fun deleteWidgetById(widgetId: Int): Boolean {
+ val widget =
+ getWidgetByIdNow(widgetId) ?: // no entry to delete from db
+ return false
+
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
+ return true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index cf2e33ce1df5..33edb800756d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -16,15 +16,34 @@
package com.android.systemui.communal.data.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Data model of media on the communal hub. */
data class CommunalMediaModel(
- val hasAnyMediaOrRecommendation: Boolean,
+ val hasActiveMediaOrRecommendation: Boolean,
val createdTimestampMillis: Long = 0L,
-) {
+) : Diffable<CommunalMediaModel> {
companion object {
val INACTIVE =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = false,
+ hasActiveMediaOrRecommendation = false,
+ )
+ }
+
+ override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
+ if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
+ row.logChange(
+ columnName = "isMediaActive",
+ value = hasActiveMediaOrRecommendation,
+ )
+ }
+
+ if (createdTimestampMillis != prevVal.createdTimestampMillis) {
+ row.logChange(
+ columnName = "mediaCreationTimestamp",
+ value = createdTimestampMillis.toString(),
)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e8a561b37d20..201be51b873c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -18,6 +18,9 @@ package com.android.systemui.communal.data.repository
import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
@@ -34,6 +37,7 @@ class CommunalMediaRepositoryImpl
@Inject
constructor(
private val mediaDataManager: MediaDataManager,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalMediaRepository {
private val mediaDataListener =
@@ -61,13 +65,18 @@ constructor(
private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
MutableStateFlow(CommunalMediaModel.INACTIVE)
- override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+ override val mediaModel: Flow<CommunalMediaModel> =
+ _mediaModel.logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ initialValue = CommunalMediaModel.INACTIVE,
+ )
private fun updateMediaModel(data: MediaData? = null) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ if (mediaDataManager.hasActiveMediaOrRecommendation()) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index c2ea2e93ce58..0e9b32ffd12a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -21,9 +21,15 @@ import android.content.SharedPreferences
import android.content.pm.UserInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -59,11 +65,21 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
+ private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+
override val isCtaDismissed: Flow<Boolean> =
userRepository.selectedUserInfo
.flatMapLatest(::observeCtaDismissState)
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCtaDismissed",
+ initialValue = false,
+ )
.stateIn(
scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
@@ -76,11 +92,13 @@ constructor(
.edit()
.putBoolean(CTA_DISMISSED_STATE, true)
.apply()
+
+ logger.i("Dismissed CTA tile")
}
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
- userFileManager
- .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ getSharedPrefsForUser(user)
+ .observe(CTA_DISMISSED_STATE)
// Emit at the start of collection to ensure we get an initial value
.onStart { emit(Unit) }
.map { getCtaDismissedState() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 046aaaa33342..4c06e97c89b0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -25,6 +25,9 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -66,6 +69,7 @@ constructor(
private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
@CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalTutorialRepository {
companion object {
@@ -94,6 +98,12 @@ constructor(
settingsState
.map { it.hubModeTutorialState }
.filterNotNull()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "tutorialSettingState",
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f36547b01802..2ac9d053f8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -54,8 +54,11 @@ interface CommunalWidgetRepository {
configurator: WidgetConfigurator? = null
) {}
- /** Delete a widget by id from app widget service and the database. */
- fun deleteWidget(widgetId: Int) {}
+ /** Delete a widget by id from the database. */
+ fun deleteWidgetFromDb(widgetId: Int) {}
+
+ /** Delete a widget by id from app widget host. */
+ fun deleteWidgetFromHost(widgetId: Int) {}
/**
* Update the order of widgets in the database.
@@ -143,9 +146,18 @@ constructor(
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
+ bgScope.launch {
+ if (communalWidgetDao.deleteWidgetById(widgetId)) {
+ logger.i("Deleted widget with id $widgetId from DB .")
+ } else {
+ logger.w("Widget with id $widgetId cannot be deleted from DB.")
+ }
+ }
+ }
+
+ override fun deleteWidgetFromHost(widgetId: Int) {
bgScope.launch {
- communalWidgetDao.deleteWidgetById(widgetId)
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 80fee640974d..950ac3c3aae6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -33,16 +33,25 @@ import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -50,6 +59,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,6 +68,7 @@ import kotlinx.coroutines.flow.map
class CommunalInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -65,8 +77,12 @@ constructor(
userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
- private val editWidgetsActivityStarter: EditWidgetsActivityStarter
+ private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ @CommunalLog logBuffer: LogBuffer,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
+ private val logger = Logger(logBuffer, "CommunalInteractor")
+
private val _editModeOpen = MutableStateFlow(false)
/** Whether edit mode is currently open. */
@@ -85,6 +101,22 @@ constructor(
or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
.distinctUntilChanged()
+ .onEach { available ->
+ logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
+ bool1 = available
+ }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalAvailable",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -129,11 +161,36 @@ constructor(
.distinctUntilChanged()
/**
- * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
- * [CommunalSceneKey.Communal].
+ * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is
+ * the [CommunalSceneKey.Communal].
+ *
+ * This will be true as soon as the desired scene is set programmatically or at whatever point
+ * during a fling that SceneTransitionLayout determines that the end state will be the communal
+ * scene. The value also does not change while flinging away until the target scene is no longer
+ * communal.
+ *
+ * If you need a flow that is only true when communal is fully showing and not in transition,
+ * use [isIdleOnCommunal].
*/
+ // TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+ communalRepository.desiredScene
+ .map { it == CommunalSceneKey.Communal }
+ .distinctUntilChanged()
+ .onEach { showing ->
+ logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCommunalShowing",
+ initialValue = false,
+ )
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
/**
* Flow that emits a boolean if the communal UI is fully visible and not in transition.
@@ -146,6 +203,16 @@ constructor(
it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
}
+ /**
+ * Flow that emits a boolean if any portion of the communal UI is visible at all.
+ *
+ * This flow will be true during any transition and when idle on the communal scene.
+ */
+ val isCommunalVisible: Flow<Boolean> =
+ communalRepository.transitionState.map {
+ !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank)
+ }
+
/** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
@@ -170,9 +237,15 @@ constructor(
configurator: WidgetConfigurator?,
) = widgetRepository.addWidget(componentName, priority, configurator)
- /** Delete a widget by id. */
- fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+ /**
+ * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
+ * function to manage the deletion from the database for uninstalled or user-deleted widgets,
+ * following the removal of a widget from the host.
+ */
+ fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
+ /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
+ fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
/**
* Reorder the widgets.
*
@@ -245,7 +318,7 @@ constructor(
)
// Add UMO
- if (media.hasAnyMediaOrRecommendation) {
+ if (media.hasActiveMediaOrRecommendation) {
ongoingContent.add(
CommunalContentModel.Umo(
createdTimestampMillis = media.createdTimestampMillis,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 309c84e4e955..1404ee20cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -22,6 +22,9 @@ import com.android.systemui.communal.data.repository.CommunalTutorialRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,6 +52,7 @@ constructor(
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
communalInteractor: CommunalInteractor,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: StateFlow<Boolean> =
@@ -61,6 +65,12 @@ constructor(
isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isTutorialAvailable",
+ initialValue = false,
+ )
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index ebcfb8bb8209..69d55815fe01 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -21,6 +21,9 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
@@ -29,6 +32,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -38,21 +42,27 @@ constructor(
private val communalInteractor: CommunalInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
+
override val isEditMode = true
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
override val reorderingWidgets: StateFlow<Boolean>
get() = _reorderingWidgets
- override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+ override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
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 d7a3705248ff..40d2d1656fbc 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,6 +21,9 @@ 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.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.controls.ui.MediaHostState
@@ -37,6 +40,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@@ -48,22 +52,30 @@ constructor(
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+ @CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
@OptIn(ExperimentalCoroutinesApi::class)
override val communalContent: Flow<List<CommunalContentModel>> =
- tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
- if (isTutorialMode) {
- return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ tutorialInteractor.isTutorialAvailable
+ .flatMapLatest { isTutorialMode ->
+ if (isTutorialMode) {
+ return@flatMapLatest flowOf(communalInteractor.tutorialContent)
+ }
+ combine(
+ communalInteractor.ongoingContent,
+ communalInteractor.widgetContent,
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
+ }
}
- combine(
- communalInteractor.ongoingContent,
- communalInteractor.widgetContent,
- communalInteractor.ctaTileContent,
- ) { ongoing, widgets, ctaTile,
- ->
- ongoing + widgets + ctaTile
+ .onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
- }
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
@@ -75,7 +87,7 @@ constructor(
with(mediaHost) {
expansion = MediaHostState.EXPANDED
expandedMatchesParentHeight = true
- showsOnlyActiveMedia = false
+ showsOnlyActiveMedia = true
falsingProtectionNeeded = false
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index fb9abeb4fbcf..6fd0fbe80fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -50,7 +50,7 @@ constructor(
.launchIn(bgScope)
appWidgetHost.appWidgetIdToRemove
- .onEach { appWidgetId -> communalInteractor.deleteWidget(appWidgetId) }
+ .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
.launchIn(bgScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index eb6dc4379db6..92e8153840eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,6 +32,9 @@ import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.res.R
import javax.inject.Inject
@@ -42,7 +45,8 @@ constructor(
private val communalViewModel: CommunalEditModeViewModel,
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
- private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
+ private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
+ @CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
@@ -54,6 +58,8 @@ constructor(
const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
+ private val logger = Logger(logBuffer, "EditWidgetsActivity")
+
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
@@ -157,11 +163,15 @@ constructor(
override fun onStart() {
super.onStart()
+
+ logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
+
+ logger.i("Stopping the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index ae9c37aa0e7b..b35bec4eead1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -17,11 +17,16 @@
package com.android.systemui.controls.panels
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+
/**
* Repository for keeping track of which packages the panel has authorized to show control panels
* (embedded activity).
*/
interface AuthorizedPanelsRepository {
+ /** Exposes the authorized panels as a [Flow] for subscribing to updates */
+ fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>>
/** A set of package names that the user has previously authorized to show panels. */
fun getAuthorizedPanels(): Set<String>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 4e935df12ac1..7c2dae34707b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -19,11 +19,16 @@ package com.android.systemui.controls.panels
import android.content.Context
import android.content.SharedPreferences
+import android.os.UserHandle
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
class AuthorizedPanelsRepositoryImpl
@Inject
@@ -33,19 +38,24 @@ constructor(
private val userTracker: UserTracker,
) : AuthorizedPanelsRepository {
+ override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
+ val prefs = instantiateSharedPrefs(user)
+ return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+ }
+
override fun getAuthorizedPanels(): Set<String> {
- return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+ return getAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle))
}
override fun getPreferredPackages(): Set<String> =
context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
override fun addAuthorizedPanels(packageNames: Set<String>) {
- addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+ addAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle), packageNames)
}
override fun removeAuthorizedPanels(packageNames: Set<String>) {
- with(instantiateSharedPrefs()) {
+ with(instantiateSharedPrefs(userTracker.userHandle)) {
val currentSet = getAuthorizedPanelsInternal(this)
edit().putStringSet(KEY, currentSet - packageNames).apply()
}
@@ -63,12 +73,12 @@ constructor(
sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
}
- private fun instantiateSharedPrefs(): SharedPreferences {
+ private fun instantiateSharedPrefs(user: UserHandle): SharedPreferences {
val sharedPref =
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
Context.MODE_PRIVATE,
- userTracker.userId,
+ user.identifier,
)
// We should add default packages when we've never run this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0baa81a12e4f..9be049400962 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -20,21 +20,18 @@ import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -43,9 +40,7 @@ class SelectedComponentRepositoryImpl
constructor(
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
- private val featureFlags: FeatureFlags,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val applicationScope: CoroutineScope
+ @Background private val bgDispatcher: CoroutineDispatcher
) : SelectedComponentRepository {
private companion object {
@@ -66,22 +61,11 @@ constructor(
override fun selectedComponentFlow(
userHandle: UserHandle
): Flow<SelectedComponentRepository.SelectedComponent?> {
- return conflatedCallbackFlow {
- val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
- applicationScope.launch(bgDispatcher) {
- if (key == PREF_COMPONENT) {
- trySend(getSelectedComponent(userHandle))
- }
- }
- }
- sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
- send(getSelectedComponent(userHandle))
- awaitClose {
- sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
- }
- }
+ val prefs = getSharedPreferencesForUser(userHandle.identifier)
+ return prefs
+ .observe(PREF_COMPONENT)
+ .onStart { emit(Unit) }
+ .map { getSelectedComponent(userHandle) }
.flowOn(bgDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94a63b1..dd186d624c84 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@ import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeModule;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045865a6..a3d6ad456e54 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 91e0547ff93d..0cab10db756f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -47,24 +47,30 @@ class HomeControlsComponentInteractor
@Inject
constructor(
private val selectedComponentRepository: SelectedComponentRepository,
- private val controlsComponent: ControlsComponent,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ controlsComponent: ControlsComponent,
+ authorizedPanelsRepository: AuthorizedPanelsRepository,
userRepository: UserRepository,
@Background private val bgScope: CoroutineScope
) {
- private val controlsListingController =
+ private val controlsListingController: ControlsListingController? =
controlsComponent.getControlsListingController().getOrNull()
/** Gets the current user's selected panel, or null if there isn't one */
- private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+ private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
userRepository.selectedUserInfo
.flatMapLatest { user ->
selectedComponentRepository.selectedComponentFlow(user.userHandle)
}
.map { if (it?.isPanel == true) it else null }
- /** Gets all the available panels which are authorized by the user */
- private fun allPanelItem(): Flow<List<PanelComponent>> {
+ /** Gets the current user's authorized panels */
+ private val allAuthorizedPanels: Flow<Set<String>> =
+ userRepository.selectedUserInfo.flatMapLatest { user ->
+ authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+ }
+
+ /** Gets all the available services from [ControlsListingController] */
+ private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
if (controlsListingController == null) {
return emptyFlow()
}
@@ -79,26 +85,38 @@ constructor(
awaitClose { controlsListingController.removeCallback(listener) }
}
.onStart { emit(controlsListingController.getCurrentServices()) }
- .map { serviceInfos ->
- val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
- serviceInfos.mapNotNull {
- val panelActivity = it.panelActivity
- if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
- PanelComponent(it.componentName, panelActivity)
- } else {
- null
- }
+ }
+
+ /** Gets all panels which are available and authorized by the user */
+ private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+ combine(
+ allAvailableServices(),
+ allAuthorizedPanels,
+ ) { serviceInfos, authorizedPanels ->
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
}
}
- }
+ }
+
val panelComponent: StateFlow<ComponentName?> =
- combine(allPanelItem(), selectedItem) { items, selected ->
+ combine(
+ allAvailableAndAuthorizedPanels,
+ selectedPanel,
+ ) { panels, selected ->
val item =
- items.firstOrNull { it.componentName == selected?.componentName }
- ?: items.firstOrNull()
+ panels.firstOrNull { it.componentName == selected?.componentName }
+ ?: panels.firstOrNull()
item?.panelActivity
}
.stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
- data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+ private data class PanelComponent(
+ val componentName: ComponentName,
+ val panelActivity: ComponentName,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index c9b56a2ebd9a..05279fcdf51c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -23,7 +23,6 @@ import android.graphics.Region;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -34,17 +33,14 @@ import javax.inject.Named;
/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
public class CommunalTouchHandler implements DreamTouchHandler {
private final int mInitiationWidth;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
private final Optional<CentralSurfaces> mCentralSurfaces;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
@Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
- mNotificationShadeWindowController = notificationShadeWindowController;
}
@Override
@@ -60,9 +56,8 @@ public class CommunalTouchHandler implements DreamTouchHandler {
}
private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
- // Force the notification shade window open (otherwise the hub won't show while swiping).
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
-
+ // Notification shade window has its own logic to be visible if the hub is open, no need to
+ // do anything here other than send touch events over.
session.registerInputListener(ev -> {
surfaces.handleDreamTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index df0566e246a8..41ce3fd11e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,9 +23,12 @@ import com.android.server.notification.Flags.crossAppPoliteNotifications
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -55,6 +58,11 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+
+ // ComposeLockscreen dependencies
+ ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token
+ ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
+ ComposeLockscreen.token dependsOn migrateClocksToBlueprint
}
private inline val politeNotifications
@@ -65,4 +73,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
private inline val keyguardBottomAreaRefactor
get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+ private inline val migrateClocksToBlueprint
+ get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c69c9ef93761..6eff79284847 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@ object Flags {
@JvmField
val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
- // TODO(b/287205379): Tracking bug
- @JvmField
- val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
-
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index d5f082a2566f..72a81cbac9d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -19,10 +19,10 @@ package com.android.systemui.keyboard.stickykeys.shared.model
@JvmInline
value class Locked(val locked: Boolean)
-enum class ModifierKey(val text: String) {
+enum class ModifierKey(val displayedText: String) {
ALT("ALT LEFT"),
ALT_GR("ALT RIGHT"),
CTRL("CTRL"),
- META("META"),
+ META("ACTION"),
SHIFT("SHIFT"),
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b5f9c69a71b0..4cabd70cb142 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@ import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -108,20 +106,7 @@ public class KeyguardService extends Service {
private final ScreenOnCoordinator mScreenOnCoordinator;
private final ShellTransitions mShellTransitions;
private final DisplayTracker mDisplayTracker;
- private PowerInteractor mPowerInteractor;
-
- private static int newModeToLegacyMode(int newMode) {
- switch (newMode) {
- case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
- return MODE_OPENING;
- case WindowManager.TRANSIT_CLOSE:
- case WindowManager.TRANSIT_TO_BACK:
- return MODE_CLOSING;
- default:
- return 2; // MODE_CHANGING
- }
- }
+ private final PowerInteractor mPowerInteractor;
private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@ public class KeyguardService extends Service {
public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
SurfaceControl.Transaction candidateT, IBinder currentTransition,
- IRemoteTransitionFinishedCallback candidateFinishCallback)
- throws RemoteException {
+ IRemoteTransitionFinishedCallback candidateFinishCallback) {
if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
keyguardViewMediator.setPendingLock(true);
keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@ public class KeyguardService extends Service {
runner.onAnimationCancelled();
finish(currentTransition);
} catch (RemoteException e) {
- // nothing, we'll just let it finish on its own I guess.
+ // Ignore.
}
}
@Override
- public void onTransitionConsumed(IBinder transition, boolean aborted)
- throws RemoteException {
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {
+ // No-op.
}
private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@ public class KeyguardService extends Service {
}
private void finish(IBinder transition) throws RemoteException {
- IRemoteTransitionFinishedCallback finishCallback = null;
+ final IRemoteTransitionFinishedCallback finishCallback;
SurfaceControl.Transaction finishTransaction = null;
synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa3725d..f085e88da954 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@ import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -326,7 +327,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
- private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
- private SystemPropertiesHelper mSystemPropertiesHelper;
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
* Used to keep the device awake while to ensure the keyguard finishes opening before
* we sleep.
*/
- private PowerManager.WakeLock mShowKeyguardWakeLock;
+ private final PowerManager.WakeLock mShowKeyguardWakeLock;
private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
@@ -405,13 +405,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private boolean mWakeAndUnlocking = false;
/**
- * Helps remember whether the screen has turned on since the last time
- * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ * Helps remember whether the screen has turned on since the last time it turned off due to
+ * timeout. See {@link #onScreenTurnedOff}
*/
private int mDelayedShowingSequence;
/**
- * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -439,7 +439,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private boolean mGoingToSleep;
// last known state of the cellular connection
- private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+ private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
* Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
@@ -1088,8 +1088,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- if (!handleOnAnimationStart(
- transit, apps, wallpapers, nonApps, finishedCallback)) {
+ if (!handleOnAnimationStart(apps, finishedCallback)) {
// Usually we rely on animation completion to synchronize occluded status,
// but there was no animation to play, so just update it now.
setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1096,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
@@ -1107,8 +1105,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo != null
- && apps[0].taskInfo.topActivityType
+ final boolean isDream = (primary.taskInfo != null
+ && primary.taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1320,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private DeviceConfigProxy mDeviceConfig;
- private DozeParameters mDozeParameters;
- private SelectedUserInteractor mSelectedUserInteractor;
- private KeyguardInteractor mKeyguardInteractor;
+ private final DozeParameters mDozeParameters;
+ private final SelectedUserInteractor mSelectedUserInteractor;
+ private final KeyguardInteractor mKeyguardInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1346,14 +1344,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
- private ScreenOnCoordinator mScreenOnCoordinator;
private final KeyguardTransitions mKeyguardTransitions;
- private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
- private Lazy<ScrimController> mScrimControllerLazy;
- private IActivityTaskManager mActivityTaskManagerService;
+ private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private final Lazy<ScrimController> mScrimControllerLazy;
+ private final IActivityTaskManager mActivityTaskManagerService;
- private FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1357,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1429,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
mDeviceConfig = deviceConfig;
- mScreenOnCoordinator = screenOnCoordinator;
mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1440,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mHandler::post,
mOnPropertiesChangedListener);
mInGestureNavigationMode =
- QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
- mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
- }));
+ QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+ mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1468,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
@@ -1578,14 +1571,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
this::setWallpaperSupportsAmbientMode);
}
- // TODO(b/273443374) remove, temporary util to get a feature flag
- private WallpaperManager getWallpaperManager() {
- if (mWallpaperManager == null) {
- mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
- }
- return mWallpaperManager;
- }
-
@Override
public void start() {
synchronized (this) {
@@ -1611,11 +1596,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+ DreamingToLockscreenTransitionViewModel viewModel =
+ mDreamingToLockscreenTransitionViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(),
- mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+ collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
}
}
@@ -2304,6 +2289,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
showKeyguard(options);
}
+ @SuppressLint("MissingPermission")
private void lockProfile(int userId) {
mTrustManager.setDeviceLockedForUser(userId, true);
}
@@ -2497,13 +2483,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
};
/**
- * This handler will be associated with the policy thread, which will also
- * be the UI thread of the keyguard. Since the apis of the policy, and therefore
- * this class, can be called by other threads, any action that directly
- * interacts with the keyguard ui should be posted to this handler, rather
- * than called directly.
+ * This handler will be associated with the policy thread, which will also be the UI thread of
+ * the keyguard. Since the apis of the policy, and therefore this class, can be called by other
+ * threads, any action that directly interacts with the keyguard ui should be posted to this
+ * handler, rather than called directly.
*/
- private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
String message = "";
@@ -2766,7 +2751,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
try {
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
- } catch (RemoteException e) {
+ } catch (RemoteException ignored) {
}
});
}
@@ -2790,9 +2775,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
return;
- } else {
- if (DEBUG) Log.d(TAG, "handleShow");
}
+ if (DEBUG) Log.d(TAG, "handleShow");
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
@@ -2851,6 +2835,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ @SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2910,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
return;
}
- final String reasonDescription;
-
- switch(reason) {
- case WakeAndUnlockUpdateReason.FULFILL:
- reasonDescription = "fulfilling existing request";
- break;
- case WakeAndUnlockUpdateReason.HIDE:
- reasonDescription = "hiding keyguard";
- break;
- case WakeAndUnlockUpdateReason.SHOW:
- reasonDescription = "showing keyguard";
- break;
- case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
- reasonDescription = "waking to unlock";
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + reason);
- }
+ final String reasonDescription = switch (reason) {
+ case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+ case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+ case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+ case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+ default -> throw new IllegalStateException("Unexpected value: " + reason);
+ };
final boolean unsetUnfulfilled = !updatedValue
&& reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3031,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback() {
@Override
- public void onAnimationFinished() throws RemoteException {
+ public void onAnimationFinished() {
if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
@@ -3542,11 +3516,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
/**
* Registers the CentralSurfaces to which the Keyguard View is mounted.
*
- * @param centralSurfaces
- * @param panelView
- * @param biometricUnlockController
- * @param notificationContainer
- * @param bypassController
* @return the View Controller for the Keyguard View this class is mediating.
*/
public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3742,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
});
updateInputRestrictedLocked();
- mUiBgExecutor.execute(() -> {
- mTrustManager.reportKeyguardShowingChanged();
- });
+ mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
private void notifyTrustedChangedLocked(boolean trusted) {
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 e16f8dcbb00e..70da3e7ad1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@ import dagger.multibindings.IntoMap;
import java.util.concurrent.Executor;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Dagger Module providing keyguard.
*/
+@ExperimentalCoroutinesApi
@Module(subcomponents = {
KeyguardQsUserSwitchComponent.class,
KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@ public interface KeyguardModule {
*/
@Provides
@SysUISingleton
- public static KeyguardViewMediator newKeyguardViewMediator(
+ static KeyguardViewMediator newKeyguardViewMediator(
Context context,
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 9a13558d3327..b152eea63028 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -174,6 +175,8 @@ constructor(
mainDispatcher
) // keyguardUpdateMonitor requires registration on main thread.
+ // TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
+ // in BiometricStatusRepository
override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
get() = conflatedCallbackFlow {
val callback =
@@ -236,7 +239,8 @@ constructor(
sendUpdateIfFingerprint(
biometricSourceType,
AcquiredFingerprintAuthenticationStatus(
- acquireInfo,
+ AuthenticationReason.DeviceEntryAuthentication,
+ acquireInfo
),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index b1a2297526ce..e017129bd5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -94,6 +94,7 @@ constructor(
context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+
return primaryBouncerInteractor.isBouncerShowing() &&
sfpsEnabled &&
sfpsDetectionRunning &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
new file mode 100644
index 000000000000..7f0b483919b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.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.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the compose lockscreen flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ComposeLockscreen {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable()
+
+ /**
+ * 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/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index cc385a8eea85..474de77f09ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -20,6 +20,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQ
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
/**
* Fingerprint authentication status provided by
@@ -40,8 +41,10 @@ data class HelpFingerprintAuthenticationStatus(
) : FingerprintAuthenticationStatus()
/** Fingerprint acquired message. */
-data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
- FingerprintAuthenticationStatus() {
+data class AcquiredFingerprintAuthenticationStatus(
+ val authenticationReason: AuthenticationReason,
+ val acquiredInfo: Int
+) : FingerprintAuthenticationStatus() {
val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6374e0..789d30ff1a31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@ object KeyguardRootViewBinder {
.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
}
}
@@ -200,6 +201,7 @@ object KeyguardRootViewBinder {
.collect { x ->
childViews[burnInLayerId]?.translationX = x
childViews[largeClockId]?.translationX = x
+ childViews[aodNotificationIconContainerId]?.translationX = x
}
}
@@ -219,6 +221,10 @@ object KeyguardRootViewBinder {
// transition with other parts in burnInLayer
childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 3d36eb03a1bc..9a1fcc1a6a51 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -41,11 +41,13 @@ constructor(
return
}
- val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+ // The burn-in layer requires at least 1 view at all times
+ val emptyView = View(context, null).apply { id = View.generateViewId() }
+ constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
- addView(nic)
+ addView(emptyView)
if (!migrateClocksToBlueprint()) {
val statusView =
constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 310ec95a22df..ad6a36c71e39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -74,7 +75,18 @@ constructor(
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
+
+ private val padding: Flow<Int> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
+ if (udfpsSupported) {
+ udfpsOverlayInteractor.iconPadding
+ } else {
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ }
+ }
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index ca9c8571f6b9..67c42f0fe343 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -22,8 +22,10 @@ import android.graphics.Point
import androidx.annotation.VisibleForTesting
import androidx.core.animation.addListener
import com.android.systemui.Flags
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -49,10 +52,12 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
@@ -62,7 +67,8 @@ class SideFpsProgressBarViewModel
@Inject
constructor(
private val context: Context,
- private val fpAuthRepository: DeviceEntryFingerprintAuthInteractor,
+ private val biometricStatusInteractor: BiometricStatusInteractor,
+ private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val sfpsSensorInteractor: SideFpsSensorInteractor,
// todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
// DozeInteractor as DozeServiceHost already depends on DozeInteractor.
@@ -86,6 +92,23 @@ constructor(
private val additionalSensorLengthPadding =
context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
+ // Merged [FingerprintAuthenticationStatus] from BiometricPrompt acquired messages and
+ // device entry authentication messages
+ private val mergedFingerprintAuthenticationStatus =
+ merge(
+ biometricStatusInteractor.fingerprintAcquiredStatus,
+ deviceEntryFingerprintAuthInteractor.authenticationStatus
+ )
+ .filter {
+ if (it is AcquiredFingerprintAuthenticationStatus) {
+ it.authenticationReason == AuthenticationReason.DeviceEntryAuthentication ||
+ it.authenticationReason ==
+ AuthenticationReason.BiometricPromptAuthentication
+ } else {
+ true
+ }
+ }
+
val isVisible: Flow<Boolean> = _visible.asStateFlow()
val progress: Flow<Float> = _progress.asStateFlow()
@@ -147,7 +170,14 @@ constructor(
viewLeftTop
}
- val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+ val isFingerprintAuthRunning: Flow<Boolean> =
+ combine(
+ deviceEntryFingerprintAuthInteractor.isRunning,
+ biometricStatusInteractor.sfpsAuthenticationReason
+ ) { deviceEntryAuthIsRunning, sfpsAuthReason ->
+ deviceEntryAuthIsRunning ||
+ sfpsAuthReason == AuthenticationReason.BiometricPromptAuthentication
+ }
val rotation: Flow<Float> =
combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
@@ -185,7 +215,8 @@ constructor(
sfpsSensorInteractor.authenticationDuration
.flatMapLatest { authDuration ->
_animator?.cancel()
- fpAuthRepository.authenticationStatus.map { authStatus ->
+ mergedFingerprintAuthenticationStatus.map {
+ authStatus: FingerprintAuthenticationStatus ->
when (authStatus) {
is AcquiredFingerprintAuthenticationStatus -> {
if (authStatus.fingerprintCaptureStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
new file mode 100644
index 000000000000..fe7dc4bdf59f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [TableLogBuffer] for communal-related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 3e0094081638..ac579d6d2491 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -579,6 +579,16 @@ public class LogModule {
return factory.create("CommunalLog", 250);
}
+ /**
+ * Provides a {@link TableLogBuffer} for communal-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @CommunalTableLog
+ public static TableLogBuffer provideCommunalTableLogBuffer(TableLogBufferFactory factory) {
+ return factory.create("CommunalTableLog", 250);
+ }
+
/** Provides a {@link LogBuffer} for display metrics related logs. */
@Provides
@SysUISingleton
@@ -618,4 +628,13 @@ public class LogModule {
public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
return factory.create("PackageChangeRepo", 50);
}
+
+ /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+ @Provides
+ @SysUISingleton
+ @NavBarButtonClickLog
+ public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+ return factory.create("NavBarButtonClick", 50);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 000000000000..939dab2ecefa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -0,0 +1,34 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 000000000000..408acf3ce1a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+ fun logHomeButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+ }
+
+ fun logBackButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+ }
+
+ fun logRecentsButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+ }
+
+ fun logImeSwitcherClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+ }
+
+ fun logAccessibilityButtonClick() {
+ buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+ }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd61ea4..95b75ac7a875 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@ import android.view.InsetsFrameProvider;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private boolean mImeVisible;
private final Rect mSamplingBounds = new Rect();
private final Binder mInsetsSourceOwner = new Binder();
+ private final NavBarButtonClickLogger mNavBarButtonClickLogger;
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
UserContextProvider userContextProvider,
WakefulnessLifecycle wakefulnessLifecycle,
TaskStackChangeListeners taskStackChangeListeners,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ NavBarButtonClickLogger navBarButtonClickLogger) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -601,6 +599,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayTracker = displayTracker;
mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
ButtonDispatcher homeButton = mView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
+ homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+ ButtonDispatcher backButton = mView.getBackButton();
+ backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
reconfigureHomeLongClick();
@@ -1388,6 +1391,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
private void onRecentsClick(View v) {
+ mNavBarButtonClickLogger.logRecentsButtonClick();
+
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
private void onImeSwitcherClick(View v) {
+ mNavBarButtonClickLogger.logImeSwitcherClick();
mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
private void onAccessibilityClick(View v) {
+ mNavBarButtonClickLogger.logAccessibilityButtonClick();
final Display display = v.getDisplay();
mAccessibilityManager.notifyAccessibilityButtonClicked(
display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abcae7eb..fc37b9f0979d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import java.util.ArrayList;
@@ -52,6 +55,7 @@ public class ButtonDispatcher {
private boolean mVertical;
private ValueAnimator mFadeAnimator;
private AccessibilityDelegate mAccessibilityDelegate;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
setAlpha(
@@ -341,4 +345,36 @@ public class ButtonDispatcher {
*/
public void onDestroy() {
}
+
+ /**
+ * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+ */
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ if (navBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ final int size = mViews.size();
+ for (int i = 0; i < size; i++) {
+ final View v = mViews.get(i);
+ setNavBarButtonClickLoggerForViewChildren(v);
+ }
+ }
+ }
+
+ /**
+ * Recursively explores view hierarchy until the children of provided view are of type
+ * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+ */
+ private void setNavBarButtonClickLoggerForViewChildren(View v) {
+ if (v instanceof KeyButtonView) {
+ ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+ return;
+ }
+
+ if (v instanceof ViewGroup viewGroup) {
+ final int childrenCount = viewGroup.getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d31ab1..dbe87eafef7b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private float mDarkIntensity;
private boolean mHasOvalBg = false;
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
@VisibleForTesting
public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mOnClickListener = onClickListener;
}
+ public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+ mNavBarButtonClickLogger = navBarButtonClickLogger;
+ }
+
public void loadAsync(Icon icon) {
new AsyncTask<Icon, Void, Drawable>() {
@Override
@@ -389,11 +395,19 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logBackButtonClick();
+ }
break;
case KeyEvent.KEYCODE_HOME:
uiEvent = longPressSet
? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
: NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+ if (mNavBarButtonClickLogger != null) {
+ mNavBarButtonClickLogger.logHomeButtonClick();
+ }
break;
case KeyEvent.KEYCODE_APP_SWITCH:
uiEvent = longPressSet
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
new file mode 100644
index 000000000000..26069c774364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.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.qs.tiles.impl.fontscaling.domain
+
+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.fontscaling.domain.model.FontScalingTileModel
+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 [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<FontScalingTileModel> {
+
+ override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ val icon =
+ Icon.Loaded(
+ resources.getDrawable(
+ R.drawable.ic_qs_font_scaling,
+ theme,
+ ),
+ contentDescription = null
+ )
+ this.icon = { icon }
+ contentDescription = label
+ activationState = QSTileState.ActivationState.ACTIVE
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 000000000000..745e6a301689
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.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.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+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.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+ QSTileDataInteractor<FontScalingTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 000000000000..b6f4afb84259
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val coroutineContext: CoroutineContext,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // We animate from the touched view only if we are not on the keyguard
+ val animateFromView: Boolean =
+ action.view != null && !keyguardStateController.isShowing
+ val runnable = Runnable {
+ val dialog: SystemUIDialog =
+ fontScalingDialogDelegateProvider.get().createDialog()
+ if (animateFromView) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ action.view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } else {
+ dialog.show()
+ }
+ }
+
+ withContext(coroutineContext) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ runnable,
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+ )
+ }
+ }
+ }
+ companion object {
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
new file mode 100644
index 000000000000..76042dffa47e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.fontscaling.domain.model
+
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 246ccb1180b0..56c0ca910bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -42,6 +42,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
@@ -51,10 +52,12 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -81,6 +84,7 @@ constructor(
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
+ private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
) : CoreStartable {
override fun start() {
@@ -112,26 +116,39 @@ constructor(
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- sceneInteractor.transitionState
- .mapNotNull { state ->
- when (state) {
- is ObservableTransitionState.Idle -> {
- if (state.scene != SceneKey.Gone) {
- true to "scene is not Gone"
- } else {
- false to "scene is Gone"
- }
- }
- is ObservableTransitionState.Transition -> {
- if (state.fromScene == SceneKey.Gone) {
- true to "scene transitioning away from Gone"
- } else {
- null
+ combine(
+ deviceProvisioningInteractor.isDeviceProvisioned,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
+ ) { isDeviceProvisioned, isFrpActive ->
+ isDeviceProvisioned && !isFrpActive
+ }
+ .distinctUntilChanged()
+ .flatMapLatest { isAllowedToBeVisible ->
+ if (isAllowedToBeVisible) {
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != SceneKey.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == SceneKey.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
+ }
+ }
}
- }
+ .distinctUntilChanged()
+ } else {
+ flowOf(false to "Device not provisioned or Factory Reset Protection active")
}
}
- .distinctUntilChanged()
.collect { (isVisible, loggingReason) ->
sceneInteractor.setVisible(isVisible, loggingReason)
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924b3959..fd807dbec581 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -29,14 +29,6 @@ import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
import dagger.Binds;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 000000000000..76d1d3dd145e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SecureSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSecureSettingsRepository(
+ contentResolver: ContentResolver,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ ): SecureSettingsRepository =
+ SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 00534743588c..f7fed537a167 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -183,7 +183,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mBackgroundExecutor = backgroundExecutor;
mColorExtractor = colorExtractor;
mScreenOffAnimationController = screenOffAnimationController;
- dumpManager.registerDumpable(this);
+ // prefix with {slow} to make sure this dumps at the END of the critical section.
+ dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
mUserInteractor = userInteractor;
mSceneContainerFlags = sceneContainerFlags;
@@ -331,8 +332,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
);
collectFlow(
mWindowRootView,
- mCommunalInteractor.get().isCommunalShowing(),
- this::onCommunalShowingChanged
+ mCommunalInteractor.get().isCommunalVisible(),
+ this::onCommunalVisibleChanged
);
}
@@ -475,6 +476,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
visible = true;
mLogger.d("Visibility forced to be true");
+ } else if (state.communalVisible) {
+ visible = true;
+ mLogger.d("Visibility forced to be true by communal");
}
if (mWindowRootView != null) {
if (visible) {
@@ -510,15 +514,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean communalVisible = state.isCommunalVisibleAndNotOccluded();
final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
long timeout = -1;
- if ((communalShowing || keyguardShowing)
+ if ((communalVisible || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
if (state.bouncerShowing) {
timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
- } else if (communalShowing) {
+ } else if (communalVisible) {
timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
} else if (keyguardShowing) {
timeout = mLockScreenDisplayTimeout;
@@ -624,7 +628,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
state.dozing,
state.scrimsVisibility,
state.backgroundBlurRadius,
- state.communalShowing
+ state.communalVisible
);
}
@@ -749,8 +753,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
@VisibleForTesting
- void onCommunalShowingChanged(Boolean showing) {
- mCurrentState.communalShowing = showing;
+ void onCommunalVisibleChanged(Boolean visible) {
+ mCurrentState.communalVisible = visible;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index f9c9d83e03aa..e0a98b3b7f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,15 +58,15 @@ class NotificationShadeWindowState(
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
- @JvmField var communalShowing: Boolean = false,
+ @JvmField var communalVisible: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
- fun isCommunalShowingAndNotOccluded(): Boolean {
- return communalShowing && !keyguardOccluded
+ fun isCommunalVisibleAndNotOccluded(): Boolean {
+ return communalVisible && !keyguardOccluded
}
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
@@ -99,7 +99,7 @@ class NotificationShadeWindowState(
dozing.toString(),
scrimsVisibility.toString(),
backgroundBlurRadius.toString(),
- communalShowing.toString(),
+ communalVisible.toString(),
)
}
@@ -140,7 +140,7 @@ class NotificationShadeWindowState(
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
- communalShowing: Boolean,
+ communalVisible: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -172,7 +172,7 @@ class NotificationShadeWindowState(
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
- this.communalShowing = communalShowing
+ this.communalVisible = communalVisible
}
}
@@ -218,7 +218,7 @@ class NotificationShadeWindowState(
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalShowing"
+ "communalVisible"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 84cad1d16d73..c0afa32571e7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -30,8 +30,6 @@ import androidx.lifecycle.lifecycleScope
import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,18 +57,17 @@ internal const val INSET_DEBOUNCE_MILLIS = 500L
@SysUISingleton
class NotificationsQSContainerController @Inject constructor(
- view: NotificationsQuickSettingsContainer,
- private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService,
- private val shadeHeaderController: ShadeHeaderController,
- private val shadeInteractor: ShadeInteractor,
- private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor,
- private val featureFlags: FeatureFlags,
- private val
- notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
- private val splitShadeStateController: SplitShadeStateController,
- private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
+ view: NotificationsQuickSettingsContainer,
+ private val navigationModeController: NavigationModeController,
+ private val overviewProxyService: OverviewProxyService,
+ private val shadeHeaderController: ShadeHeaderController,
+ private val shadeInteractor: ShadeInteractor,
+ private val fragmentService: FragmentService,
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ private val splitShadeStateController: SplitShadeStateController,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var splitShadeEnabled = false
@@ -133,9 +130,6 @@ class NotificationsQSContainerController @Inject constructor(
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
- if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
- mView.enableGraphOptimization()
- }
}
public override fun onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index de3d16a57a1f..25e558ee42dd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,10 +32,10 @@ import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.res.R;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import java.util.ArrayList;
@@ -73,6 +73,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
+ setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
}
@Override
@@ -180,10 +181,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
super.dispatchDraw(canvas);
}
- void enableGraphOptimization() {
- setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 10b9db0a349b..4e8b4039cc79 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -20,6 +20,7 @@ import android.view.MotionEvent
import com.android.systemui.assist.AssistManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
@@ -34,11 +35,13 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Implementation of ShadeController backed by scenes instead of NPVC.
@@ -50,6 +53,7 @@ import kotlinx.coroutines.launch
class ShadeControllerSceneImpl
@Inject
constructor(
+ @Main private val mainDispatcher: CoroutineDispatcher,
@Background private val scope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
@@ -193,7 +197,11 @@ constructor(
}
override fun setVisibilityListener(listener: ShadeVisibilityListener) {
- scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+ scope.launch {
+ sceneInteractor.isVisible.collect { isVisible ->
+ withContext(mainDispatcher) { listener.expandedVisibleChanged(isVisible) }
+ }
+ }
}
@ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index a71cf950cbe1..e619806abc34 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,9 +25,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -44,7 +45,7 @@ class ShadeInteractorImpl
@Inject
constructor(
@Application val scope: CoroutineScope,
- deviceProvisioningRepository: DeviceProvisioningRepository,
+ deviceProvisioningInteractor: DeviceProvisioningInteractor,
disableFlagsRepository: DisableFlagsRepository,
dozeParams: DozeParameters,
keyguardRepository: KeyguardRepository,
@@ -56,7 +57,7 @@ constructor(
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
combine(
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
disableFlagsRepository.disableFlags,
) { isFrpActive, isDisabledByFlags ->
isDisabledByFlags.isShadeEnabled() && !isFrpActive
@@ -83,7 +84,7 @@ constructor(
powerInteractor.isAsleep,
keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningRepository.isFactoryResetProtectionActive,
+ deviceProvisioningInteractor.isFactoryResetProtectionActive,
) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
when {
// Touches are disabled when Factory Reset Protection is active
@@ -103,7 +104,7 @@ constructor(
isShadeEnabled,
keyguardRepository.isDozing,
userSetupRepository.isUserSetUp,
- deviceProvisioningRepository.isDeviceProvisioned,
+ deviceProvisioningInteractor.isDeviceProvisioned,
) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
isDeviceProvisioned &&
// Disallow QS during setup if it's a simple user switcher. (The user intends to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f8be1cc7e55..ef5026538216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,6 +14,7 @@ import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.ExpandHelper
+import com.android.systemui.Flags.nsslFalsingFix
import com.android.systemui.Gefingerpoken
import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
import com.android.systemui.classifier.Classifier
@@ -889,7 +890,7 @@ class DragDownHelper(
isDraggingDown = false
isTrackpadReverseScroll = false
shadeRepository.setLegacyLockscreenShadeTracking(false)
- if (KeyguardShadeMigrationNssl.isEnabled) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) {
return true
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 32cd56ca223f..b64e0b7d3187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@ import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -73,7 +72,7 @@ import dagger.multibindings.IntoMap;
* their own version of CentralSurfaces can include just dependencies, without injecting
* CentralSurfaces itself.
*/
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
public interface CentralSurfacesDependenciesModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e525d1..27536bce97f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@ package com.android.systemui.statusbar.dagger;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import dagger.Module;
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/** */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+ StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
NotificationsModule.class, NotificationRowModule.class})
public interface CentralSurfacesModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27775b8..29627e14e4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816aea452b..954e80505cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 6bba72b2cd49..92b0c048f3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@@ -94,6 +95,7 @@ import dagger.multibindings.IntoMap;
FooterViewModelModule.class,
KeyguardNotificationVisibilityProviderModule.class,
NotificationDataLayerModule.class,
+ NotificationDomainLayerModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
ActivatableNotificationViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2cac0002f013..b187cf15cccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -17,4 +17,10 @@ package com.android.systemui.statusbar.notification.data
import dagger.Module
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+ includes =
+ [
+ NotificationSettingsRepositoryModule::class,
+ ]
+)
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
new file mode 100644
index 000000000000..a7970c70e4ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsRepository(
+ @Background backgroundScope: CoroutineScope,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ secureSettingsRepository: SecureSettingsRepository,
+ ): NotificationSettingsRepository =
+ NotificationSettingsRepository(
+ backgroundScope,
+ backgroundDispatcher,
+ secureSettingsRepository
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
new file mode 100644
index 000000000000..5c49b28f343f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain
+
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 000000000000..0a9e12a23b8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+ @Provides
+ @SysUISingleton
+ fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+ NotificationSettingsInteractor(repository)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3616fd6d8cd1..16f18a3c3fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -54,7 +54,7 @@ public class FooterView extends StackScrollerDecorView {
private static final String TAG = "FooterView";
private FooterViewButton mClearAllButton;
- private FooterViewButton mManageButton;
+ private FooterViewButton mManageOrHistoryButton;
private boolean mShowHistory;
// String cache, for performance reasons.
// Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@ public class FooterView extends StackScrollerDecorView {
private @StringRes int mClearAllButtonTextId;
private @StringRes int mClearAllButtonDescriptionId;
+ private @StringRes int mManageOrHistoryButtonTextId;
+ private @StringRes int mManageOrHistoryButtonDescriptionId;
private @StringRes int mMessageStringId;
private @DrawableRes int mMessageIconId;
@@ -155,6 +157,43 @@ public class FooterView extends StackScrollerDecorView {
mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
}
+ /** Set the text label for the "Manage"/"History" button. */
+ public void setManageOrHistoryButtonText(@StringRes int textId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mManageOrHistoryButtonTextId == textId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonTextId = textId;
+ updateManageOrHistoryButtonText();
+ }
+
+ private void updateManageOrHistoryButtonText() {
+ if (mManageOrHistoryButtonTextId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+ }
+
+ /** Set the accessibility content description for the "Clear all" button. */
+ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+ return; // nothing changed
+ }
+ mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+ updateManageOrHistoryButtonDescription();
+ }
+
+ private void updateManageOrHistoryButtonDescription() {
+ if (mManageOrHistoryButtonDescriptionId == 0) {
+ return; // not initialized yet
+ }
+ mManageOrHistoryButton.setContentDescription(
+ getContext().getString(mManageOrHistoryButtonDescriptionId));
+ }
+
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@ public class FooterView extends StackScrollerDecorView {
mSeenNotifsFooterTextView.setText(messageString);
}
-
/** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
public void setMessageIcon(@DrawableRes int iconId) {
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@ public class FooterView extends StackScrollerDecorView {
protected void onFinishInflate() {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
- mManageButton = findViewById(R.id.manage_text);
+ mManageOrHistoryButton = findViewById(R.id.manage_text);
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
updateColors();
}
@@ -213,11 +253,11 @@ public class FooterView extends StackScrollerDecorView {
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
if (isVisible) {
- mManageButton.setVisibility(View.GONE);
+ mManageOrHistoryButton.setVisibility(View.GONE);
mClearAllButton.setVisibility(View.GONE);
mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
- mManageButton.setVisibility(View.VISIBLE);
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
mClearAllButton.setVisibility(View.VISIBLE);
mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
@@ -225,7 +265,7 @@ public class FooterView extends StackScrollerDecorView {
/** Set onClickListener for the manage/history button. */
public void setManageButtonClickListener(OnClickListener listener) {
- mManageButton.setOnClickListener(listener);
+ mManageOrHistoryButton.setOnClickListener(listener);
}
/** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@ public class FooterView extends StackScrollerDecorView {
/** Show "History" instead of "Manage" on the start button. */
public void showHistory(boolean showHistory) {
+ FooterViewRefactor.assertInLegacyMode();
if (mShowHistory == showHistory) {
return;
}
@@ -260,17 +301,13 @@ public class FooterView extends StackScrollerDecorView {
}
private void updateContent() {
- if (mShowHistory) {
- mManageButton.setText(mManageNotificationHistoryText);
- mManageButton.setContentDescription(mManageNotificationHistoryText);
- } else {
- mManageButton.setText(mManageNotificationText);
- mManageButton.setContentDescription(mManageNotificationText);
- }
if (FooterViewRefactor.isEnabled()) {
updateClearAllButtonText();
updateClearAllButtonDescription();
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
+
updateMessageString();
updateMessageIcon();
} else {
@@ -285,6 +322,14 @@ public class FooterView extends StackScrollerDecorView {
// `updateResources`, which will eventually be removed. There are, however, still
// situations in which we want to update the views even if the resource IDs didn't
// change, such as configuration changes.
+ if (mShowHistory) {
+ mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+ } else {
+ mManageOrHistoryButton.setText(mManageNotificationText);
+ mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+ }
+
mClearAllButton.setText(R.string.clear_all_notifications_text);
mClearAllButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@ public class FooterView extends StackScrollerDecorView {
/** Whether the start button shows "History" (true) or "Manage" (false). */
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mShowHistory;
}
@@ -304,7 +350,9 @@ public class FooterView extends StackScrollerDecorView {
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateColors();
- updateResources();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateResources();
+ }
updateContent();
}
@@ -328,23 +376,22 @@ public class FooterView extends StackScrollerDecorView {
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
- mManageButton.setBackground(manageBg);
- mManageButton.setTextColor(onSurface);
+ mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setTextColor(onSurface);
mSeenNotifsFooterTextView.setTextColor(onSurface);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
}
private void updateResources() {
+ FooterViewRefactor.assertInLegacyMode();
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- if (!FooterViewRefactor.isEnabled()) {
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
- }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96d8f0a..9fb453afb55e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@ import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [FooterView] to its [view model][FooterViewModel]. */
object FooterViewBinder {
- fun bind(
+ fun bindWhileAttached(
footer: FooterView,
viewModel: FooterViewModel,
clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
): DisposableHandle {
- // Bind the resource IDs
- footer.setMessageString(viewModel.message.messageId)
- footer.setMessageIcon(viewModel.message.iconId)
- footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
- footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
+ return footer.repeatWhenAttached {
+ lifecycleScope.launch {
+ bind(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ }
+ }
+
+ suspend fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener
+ ) = coroutineScope {
+ launch {
+ bindClearAllButton(
+ footer,
+ viewModel,
+ clearAllNotifications,
+ )
+ }
+ launch {
+ bindManageOrHistoryButton(
+ footer,
+ viewModel,
+ launchNotificationSettings,
+ launchNotificationHistory
+ )
+ }
+ launch { bindMessage(footer, viewModel) }
+ }
- // Bind the click listeners
+ private suspend fun bindClearAllButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ clearAllNotifications: View.OnClickListener,
+ ) = coroutineScope {
footer.setClearAllButtonClickListener(clearAllNotifications)
- // Listen for visibility changes when the view is attached.
- return footer.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.clearAllButton.isVisible.collect { isVisible ->
- if (isVisible.isAnimating) {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ true,
- ) { _ ->
- isVisible.stopAnimating()
- }
- } else {
- footer.setClearAllButtonVisible(
- isVisible.value,
- /* animate = */ false,
- )
+ launch {
+ viewModel.clearAllButton.labelId.collect { textId ->
+ footer.setClearAllButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setClearAllButtonDescription(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.isVisible.collect { isVisible ->
+ if (isVisible.isAnimating) {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ true,
+ ) { _ ->
+ isVisible.stopAnimating()
}
+ } else {
+ footer.setClearAllButtonVisible(
+ isVisible.value,
+ /* animate = */ false,
+ )
}
}
+ }
+ }
- lifecycleScope.launch {
- viewModel.message.isVisible.collect { visible ->
- footer.setFooterLabelVisible(visible)
+ private suspend fun bindManageOrHistoryButton(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ launchNotificationSettings: View.OnClickListener,
+ launchNotificationHistory: View.OnClickListener,
+ ) = coroutineScope {
+ launch {
+ viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ footer.setManageButtonClickListener(launchNotificationHistory)
+ } else {
+ footer.setManageButtonClickListener(launchNotificationSettings)
}
}
}
+
+ launch {
+ viewModel.manageOrHistoryButton.labelId.collect { textId ->
+ footer.setManageOrHistoryButtonText(textId)
+ }
+ }
+
+ launch {
+ viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ footer.setManageOrHistoryButtonDescription(textId)
+ }
+ }
+
+ // NOTE: The manage/history button is always visible as long as the footer is visible, no
+ // need to update the visibility here.
+ }
+
+ private suspend fun bindMessage(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ) = coroutineScope {
+ // Bind the resource IDs
+ footer.setMessageString(viewModel.message.messageId)
+ footer.setMessageIcon(viewModel.message.iconId)
+
+ launch {
+ viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a3d73b..691dc4297145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@ import com.android.systemui.util.ui.AnimatedValue
import kotlinx.coroutines.flow.Flow
data class FooterButtonViewModel(
- @StringRes val labelId: Int,
- @StringRes val accessibilityDescriptionId: Int,
+ @StringRes val labelId: Flow<Int>,
+ @StringRes val accessibilityDescriptionId: Flow<Int>,
val isVisible: Flow<AnimatedValue<Boolean>>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abcfad65..5111c11ac584 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import dagger.Module
import dagger.Provides
import java.util.Optional
import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
class FooterViewModel(
activeNotificationsInteractor: ActiveNotificationsInteractor,
+ notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
- labelId = R.string.clear_all_notifications_text,
- accessibilityDescriptionId = R.string.accessibility_clear_all,
+ labelId = flowOf(R.string.clear_all_notifications_text),
+ accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
activeNotificationsInteractor.hasClearableNotifications
.sample(
@@ -59,6 +65,22 @@ class FooterViewModel(
.toAnimatedValueFlow(),
)
+ val manageButtonShouldLaunchHistory =
+ notificationSettingsInteractor.isNotificationHistoryEnabled
+
+ private val manageOrHistoryButtonText: Flow<Int> =
+ manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+ if (shouldLaunchHistory) R.string.manage_notifications_history_text
+ else R.string.manage_notifications_text
+ }
+
+ val manageOrHistoryButton: FooterButtonViewModel =
+ FooterButtonViewModel(
+ labelId = manageOrHistoryButtonText,
+ accessibilityDescriptionId = manageOrHistoryButtonText,
+ isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+ )
+
val message: FooterMessageViewModel =
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@ object FooterViewModelModule {
@SysUISingleton
fun provideOptional(
activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+ notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
shadeInteractor: Provider<ShadeInteractor>,
): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@ object FooterViewModelModule {
Optional.of(
FooterViewModel(
activeNotificationsInteractor.get(),
+ notificationSettingsInteractor.get(),
seenNotificationsInteractor.get(),
shadeInteractor.get()
)
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 b9afb1409d91..5e0110b8af03 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
@@ -4698,6 +4698,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* this will return false.
**/
public boolean isHistoryShown() {
+ FooterViewRefactor.assertInLegacyMode();
return mFooterView != null && mFooterView.isHistoryShown();
}
@@ -4710,10 +4711,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
mFooterView = footerView;
addView(mFooterView, index);
- if (mManageButtonClickListener != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
if (!FooterViewRefactor.isEnabled()) {
+ if (mManageButtonClickListener != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
mFooterView.setClearAllButtonClickListener(v -> {
if (mFooterClearAllListener != null) {
mFooterClearAllListener.onClearAll();
@@ -4794,8 +4795,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
- mFooterView.showHistory(showHistory);
if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.showHistory(showHistory);
mFooterView.setClearAllButtonVisible(showDismissView, animate);
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
@@ -5490,6 +5491,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
*/
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+ FooterViewRefactor.assertInLegacyMode();
mManageButtonClickListener = listener;
if (mFooterView != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
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 ed266772ac15..a2ff406b9599 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
@@ -21,8 +21,9 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.nsslFalsingFix;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +846,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
- mView.setManageButtonClickListener(v -> {
- if (mNotificationActivityStarter != null) {
- mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
- }
- });
+ if (!FooterViewRefactor.isEnabled()) {
+ mView.setManageButtonClickListener(v -> {
+ if (mNotificationActivityStarter != null) {
+ mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+ }
+ });
+ }
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -2052,7 +2055,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
boolean horizontalSwipeWantsIt = false;
boolean scrollerWantsIt = false;
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) {
// Reverse the order relative to the else statement. onScrollTouch will reset on an
// UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
if (mLongPressedView == null && !mView.isBeingDragged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e27455..4d65b9d9c792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@ constructor(
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
+ private val loggerOptional: Optional<NotificationStatsLogger>,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
- private val loggerOptional: Optional<NotificationStatsLogger>,
+ // Using a provider to avoid a circular dependency.
+ private val notificationActivityStarter: Provider<NotificationActivityStarter>,
private val viewModel: NotificationListViewModel,
) {
@@ -115,7 +119,7 @@ constructor(
) { footerView: FooterView ->
traceSection("bind FooterView") {
val disposableHandle =
- FooterViewBinder.bind(
+ FooterViewBinder.bindWhileAttached(
footerView,
footerViewModel,
clearAllNotifications = {
@@ -124,6 +128,16 @@ constructor(
)
parentView.clearAllNotifications()
},
+ launchNotificationSettings = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ false)
+ },
+ launchNotificationHistory = { view ->
+ notificationActivityStarter
+ .get()
+ .startHistoryIntent(view, /* showHistory = */ true)
+ },
)
parentView.setFooterView(footerView)
return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 3c4ca4465874..11e374f24f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Trace;
+import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -46,8 +47,9 @@ public class SensitiveNotificationProtectionControllerImpl
public void onStart(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStart");
- mProjection = info;
- mListeners.forEach(Runnable::run);
+ // Only enable sensitive content protection if sharing full screen
+ // Launch cookie only set (non-null) if sharing single app/task
+ updateProjectionState((info.getLaunchCookie() == null) ? info : null);
Trace.endSection();
}
@@ -55,10 +57,22 @@ public class SensitiveNotificationProtectionControllerImpl
public void onStop(MediaProjectionInfo info) {
Trace.beginSection(
"SNPC.onProjectionStop");
- mProjection = null;
- mListeners.forEach(Runnable::run);
+ updateProjectionState(null);
Trace.endSection();
}
+
+ private void updateProjectionState(MediaProjectionInfo info) {
+ // capture previous state
+ boolean wasSensitive = isSensitiveStateActive();
+
+ // update internal state
+ mProjection = info;
+
+ // if either previous or new state is sensitive, notify listeners.
+ if (wasSensitive || isSensitiveStateActive()) {
+ mListeners.forEach(Runnable::run);
+ }
+ }
};
@Inject
@@ -86,7 +100,6 @@ public class SensitiveNotificationProtectionControllerImpl
public boolean isSensitiveStateActive() {
// TODO(b/316955558): Add disabled by developer option
// TODO(b/316955306): Add feature exemption for sysui and bug handlers
- // TODO(b/316955346): Add feature exemption for single app screen sharing
return mProjection != null;
}
@@ -96,9 +109,18 @@ public class SensitiveNotificationProtectionControllerImpl
return false;
}
+ MediaProjectionInfo projection = mProjection;
+ if (projection == null) {
+ return false;
+ }
+
// Exempt foreground service notifications from protection in effort to keep screen share
// stop actions easily accessible
- // TODO(b/316955208): Exempt FGS notifications only for app that started projection
- return !entry.getSbn().getNotification().isFgsOrUij();
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.getNotification().isFgsOrUij()) {
+ return !sbn.getPackageName().equals(projection.getPackageName());
+ }
+
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
new file mode 100644
index 000000000000..32cf86d8e390
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.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.statusbar.policy.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates device-provisioning related business logic. */
+@SysUISingleton
+class DeviceProvisioningInteractor
+@Inject
+constructor(
+ repository: DeviceProvisioningRepository,
+) {
+ /**
+ * Whether this device has been provisioned.
+ *
+ * @see android.provider.Settings.Global.DEVICE_PROVISIONED
+ */
+ val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
+
+ /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
+ val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
index b09bfe21e014..ab6a37bccc11 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -12,32 +12,28 @@
* 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.settings
+package com.android.systemui.util.kotlin
-import android.annotation.UserIdInt
-import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
-/** Extension functions for [UserFileManager]. */
-object UserFileManagerExt {
-
- /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
- fun UserFileManager.observeSharedPreferences(
- fileName: String,
- @Context.PreferencesMode mode: Int,
- @UserIdInt userId: Int
- ): Flow<Unit> = conflatedCallbackFlow {
- val sharedPrefs = getSharedPreferences(fileName, mode, userId)
-
- val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
- }
+object SharedPreferencesExt {
+ /**
+ * Returns a flow of [Unit] that is invoked each time shared preference is updated.
+ *
+ * @param key Optional key to limit updates to a particular key.
+ */
+ fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
+ conflatedCallbackFlow {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
+ registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a2917e..ff1daea4816e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dagger
+import android.content.Context
import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@ interface AudioModule {
@Provides
fun provideAudioRepository(
+ @Application context: Context,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
- ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+ ): AudioRepository =
+ AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
@Provides
fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 8299acbc2d52..375ebe86ae29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,6 +51,7 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.animation.AccelerateInterpolator;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -79,6 +80,7 @@ import java.util.function.Supplier;
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@FlakyTest(bugId = 308501761)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 8f0e910271c7..8fbeb6f93360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
@@ -24,11 +25,13 @@ import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -167,6 +170,28 @@ class BiometricStatusRepositoryTest : SysuiTestCase() {
listener.onAuthenticationStopped()
assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
}
+
+ @Test
+ fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+ testScope.runTest {
+ val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+ runCurrent()
+
+ val listener = biometricManager.captureListener()
+ listener.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT,
+ REASON_AUTH_BP,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+
+ assertThat(fingerprintAcquiredStatus)
+ .isEqualTo(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ }
}
private fun BiometricManager.captureListener() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index d7b7d79425c8..5c34fd9a1bc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,12 +19,14 @@ package com.android.systemui.biometrics.domain.interactor
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
+import android.hardware.biometrics.BiometricFingerprintConstants
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -162,6 +164,27 @@ class BiometricStatusInteractorImplTest : SysuiTestCase() {
)
assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
}
+
+ @Test
+ fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+ testScope.runTest {
+ val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+ runCurrent()
+
+ biometricStatusRepository.setFingerprintAcquiredStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ assertThat(fingerprintAcquiredStatus)
+ .isEqualTo(
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.BiometricPromptAuthentication,
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+ }
}
private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 3603c3c6c46a..5509c048b0da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,6 +58,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -100,6 +101,7 @@ import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -253,7 +255,8 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
- mock(),
+ biometricStatusInteractor,
+ kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
kosmos.dozeServiceHost,
kosmos.keyguardInteractor,
@@ -426,6 +429,54 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
}
}
+ // On progress bar shown - hide indicator
+ // On progress bar hidden - show indicator
+ @Test
+ fun verifyIndicatorProgressBarInteraction() {
+ testScope.runTest {
+ // Pre-auth conditions
+ setupTestConfiguration(
+ DeviceConfig.X_ALIGNED,
+ rotation = DisplayRotation.ROTATION_0,
+ isInRearDisplayMode = false
+ )
+ biometricStatusRepository.setFingerprintAuthenticationReason(
+ AuthenticationReason.NotRunning
+ )
+ sideFpsProgressBarViewModel.setVisible(false)
+
+ // Show primary bouncer
+ updatePrimaryBouncer(
+ isShowing = true,
+ isAnimatingAway = false,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = true
+ )
+ runCurrent()
+
+ val inOrder = inOrder(windowManager)
+
+ // Verify indicator shown
+ inOrder.verify(windowManager).addView(any(), any())
+
+ // Set progress bar visible
+ sideFpsProgressBarViewModel.setVisible(true)
+
+ runCurrent()
+
+ // Verify indicator hidden
+ inOrder.verify(windowManager).removeView(any())
+
+ // Set progress bar invisible
+ sideFpsProgressBarViewModel.setVisible(false)
+
+ runCurrent()
+
+ // Verify indicator shown
+ inOrder.verify(windowManager).addView(any(), any())
+ }
+ }
+
private fun updatePrimaryBouncer(
isShowing: Boolean,
isAnimatingAway: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6a9c88151dd0..2e94d381b8dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -81,6 +81,7 @@ private const val USER_ID = 4
private const val CHALLENGE = 2L
private const val DELAY = 1000L
private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -1246,6 +1247,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
+ fun logoIsNullIfPackageNameNotFound() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isNull()
+ }
+
+ @Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
@@ -1291,7 +1300,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
contentView: PromptContentView? = null,
logoRes: Int = -1,
logoBitmap: Bitmap? = null,
- block: suspend TestScope.() -> Unit
+ packageName: String = OP_PACKAGE_NAME,
+ block: suspend TestScope.() -> Unit,
) {
selector.initializePrompt(
requireConfirmation = testCase.confirmationRequested,
@@ -1302,6 +1312,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
contentViewFromApp = contentView,
logoResFromApp = logoRes,
logoBitmapFromApp = logoBitmap,
+ packageName = packageName,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1481,6 +1492,7 @@ private fun PromptSelectorInteractor.initializePrompt(
contentViewFromApp: PromptContentView? = null,
logoResFromApp: Int = -1,
logoBitmapFromApp: Bitmap? = null,
+ packageName: String = OP_PACKAGE_NAME,
) {
val info =
PromptInfo().apply {
@@ -1500,7 +1512,7 @@ private fun PromptSelectorInteractor.initializePrompt(
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
- OP_PACKAGE_NAME,
+ packageName,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 3c430316ffbe..2014755bd964 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics.ui.viewmodel
-import android.app.ActivityTaskManager
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.Color
@@ -39,10 +38,10 @@ import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -80,7 +79,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -109,7 +107,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock
private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@@ -147,7 +144,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400)
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private lateinit var biometricStatusInteractor: BiometricStatusInteractor
private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
private lateinit var displayStateInteractor: DisplayStateInteractorImpl
private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@@ -184,6 +180,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
.thenReturn(
Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
)
+ kosmos.biometricStatusRepository = biometricStatusRepository
alternateBouncerInteractor =
AlternateBouncerInteractor(
@@ -197,9 +194,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
testScope.backgroundScope,
)
- biometricStatusInteractor =
- BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
-
displayStateInteractor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
@@ -256,6 +250,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
+ kosmos.biometricStatusInteractor,
kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
kosmos.dozeServiceHost,
@@ -263,13 +258,13 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
displayStateInteractor,
kosmos.testDispatcher,
testScope.backgroundScope,
- kosmos.powerInteractor,
+ kosmos.powerInteractor
)
underTest =
SideFpsOverlayViewModel(
mContext,
- biometricStatusInteractor,
+ kosmos.biometricStatusInteractor,
deviceEntrySideFpsOverlayInteractor,
displayStateInteractor,
sfpsSensorInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 9c5cd713dffb..20dd913550d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -77,6 +77,18 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
@Test
+ fun deleteWidget_notInDb_returnsFalse() =
+ testScope.runTest {
+ val (widgetId, provider, priority) = widgetInfo1
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
+ }
+
+ @Test
fun addWidget_emitsActiveWidgetsInDb(): Unit =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c98d5374311d..de455f6374f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,11 +34,12 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -69,12 +70,13 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
-import java.util.*
+import java.util.Optional
import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@Mock
private lateinit var uiController: ControlsUiController
@@ -109,8 +111,6 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
- private val preferredPanelRepository = FakeSelectedComponentRepository()
-
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -171,7 +171,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
wrapper,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -225,7 +225,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
@@ -245,7 +245,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
mContext,
delayableExecutor,
uiController,
- preferredPanelRepository,
+ kosmos.selectedComponentRepository,
bindingController,
listingController,
userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 4828ba37ecb5..18ce4a8e1b7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -18,36 +18,40 @@
package com.android.systemui.controls.panels
import android.content.SharedPreferences
+import android.content.pm.UserInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.File
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
- @Mock private lateinit var userTracker: UserTracker
+ private lateinit var userTracker: FakeUserTracker
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
- whenever(userTracker.userId).thenReturn(0)
+ userTracker = kosmos.fakeUserTracker.apply { set(listOf(PRIMARY_USER, SECONDARY_USER), 0) }
}
@Test
@@ -91,7 +95,7 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
val repository = createRepository(fileManager)
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
- whenever(userTracker.userId).thenReturn(1)
+ userTracker.set(listOf(SECONDARY_USER), 0)
assertThat(repository.getAuthorizedPanels()).isEmpty()
}
@@ -127,6 +131,51 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
+ @Test
+ fun observeAuthorizedPanels() =
+ testScope.runTest {
+ val sharedPrefs = FakeSharedPreferences()
+ val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(PRIMARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+
+ repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+ }
+
+ @Test
+ fun observeAuthorizedPanelsForAnotherUser() =
+ testScope.runTest {
+ val fileManager =
+ FakeUserFileManager(
+ mapOf(
+ 0 to FakeSharedPreferences(),
+ 1 to FakeSharedPreferences(),
+ )
+ )
+ val repository = createRepository(fileManager)
+
+ val authorizedPanels by
+ collectLastValue(repository.observeAuthorizedPanels(SECONDARY_USER.userHandle))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Primary user is active, add authorized panels.
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).isEmpty()
+
+ // Make secondary user active and add authorized panels again.
+ userTracker.set(listOf(PRIMARY_USER, SECONDARY_USER), 1)
+ assertThat(authorizedPanels).isEmpty()
+ repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+ }
+
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
}
@@ -153,5 +202,9 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
private const val FILE_NAME = "controls_prefs"
private const val KEY = "authorized_panels"
private const val TEST_PACKAGE = "package"
+ private val PRIMARY_USER =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ private val SECONDARY_USER =
+ UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index b463adf40a91..a7e7ba97b5e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -23,8 +23,6 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserFileManager
@@ -74,7 +72,6 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
@Mock private lateinit var userTracker: UserTracker
private lateinit var userFileManager: UserFileManager
- private val featureFlags = FakeFeatureFlags()
// under test
private lateinit var repository: SelectedComponentRepository
@@ -95,11 +92,9 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
)
repository =
SelectedComponentRepositoryImpl(
- userFileManager,
- userTracker,
- featureFlags,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
bgDispatcher = testDispatcher,
- applicationScope = applicationCoroutineScope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bcef67e76ea1..94ea799bc90b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -38,8 +38,8 @@ import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -87,7 +87,7 @@ class ControlsStartableTest : SysuiTestCase() {
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
+ private lateinit var preferredPanelsRepository: SelectedComponentRepository
private lateinit var fakeExecutor: FakeExecutor
@@ -99,7 +99,7 @@ class ControlsStartableTest : SysuiTestCase() {
whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
fakeExecutor = FakeExecutor(FakeSystemClock())
- preferredPanelsRepository = FakeSelectedComponentRepository()
+ preferredPanelsRepository = kosmos.selectedComponentRepository
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 36ae0c740c48..8f3813d49b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -43,8 +43,8 @@ import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
@@ -53,6 +53,7 @@ import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -85,6 +86,8 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsUiControllerImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock lateinit var controlsController: ControlsController
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@@ -100,7 +103,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
- private val preferredPanelRepository = FakeSelectedComponentRepository()
+ private val preferredPanelRepository = kosmos.selectedComponentRepository
private lateinit var fakeDialogController: FakeSystemUIDialogController
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 437a35f2fab5..e3c4c2858b3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -475,10 +475,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// Then the device name is the PhoneMediaDevice string
val data = captureDeviceData(KEY)
- assertThat(data.name)
- .isEqualTo(
- context.getString(com.android.settingslib.R.string.media_transfer_this_device_name)
- )
+ assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 239bf659b1b4..edba902d5886 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
@@ -52,8 +53,6 @@ import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
@@ -106,8 +105,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val testScope = kosmos.testScope
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b13dd0..0d1e87433c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private UiEventLogger mUiEventLogger;
@Mock
+ private NavBarButtonClickLogger mNavBarButtonClickLogger;
+ @Mock
private ViewTreeObserver mViewTreeObserver;
NavBarHelper mNavBarHelper;
@Mock
@@ -596,7 +598,8 @@ public class NavigationBarTest extends SysuiTestCase {
mUserContextProvider,
mWakefulnessLifecycle,
mTaskStackChangeListeners,
- new FakeDisplayTracker(mContext)));
+ new FakeDisplayTracker(mContext),
+ mNavBarButtonClickLogger));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index a6e240b1b701..08319719bee6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -44,6 +44,7 @@ import org.junit.Assert.assertThrows
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -51,6 +52,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@Ignore("b/323053208")
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b3e386e69905..cc27cbd9d809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -189,7 +189,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -411,7 +410,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
mFakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 461db8e6166c..7f4508a11c0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -99,7 +99,6 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -260,7 +259,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mock(DozeParameters.class),
keyguardRepository,
@@ -452,11 +451,11 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
- public void setCommunalShowing_userTimeout() {
+ public void setCommunalVisible_userTimeout() {
setKeyguardShowing();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ mNotificationShadeWindowController.onCommunalVisibleChanged(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat(mLayoutParameters.getValue().userActivityTimeout)
.isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 697b05aa9add..c2261211b339 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -28,8 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
@@ -94,7 +92,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -106,7 +103,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -123,7 +119,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -536,7 +531,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index e66251a030a3..c32635020ddc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -28,8 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -91,7 +89,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
lateinit var underTest: NotificationsQSContainerController
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
@@ -104,7 +101,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
- featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -122,7 +118,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
@@ -513,7 +508,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
shadeInteractor,
fragmentService,
delayableExecutor,
- featureFlags,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 3e0a647d464e..7bd9d92baf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -103,7 +103,6 @@ import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -205,9 +204,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mStatusBarStateController = mKosmos.getStatusBarStateController();
mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
- FakeDeviceProvisioningRepository deviceProvisioningRepository =
- new FakeDeviceProvisioningRepository();
- deviceProvisioningRepository.setDeviceProvisioned(true);
+ mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
@@ -294,7 +291,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
mDisableFlagsRepository,
mDozeParameters,
mKeyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349bea390f..0dd988d424b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() {
underTest.setShowNotificationsOnLockscreenEnabled(false)
assertThat(showNotifs).isEqualTo(false)
}
+
+ @Test
+ fun testGetIsNotificationHistoryEnabled() =
+ testScope.runTest {
+ val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 1,
+ )
+ assertThat(historyEnabled).isEqualTo(true)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ value = 0,
+ )
+ assertThat(historyEnabled).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8cb064dd39a6..545053711179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -56,8 +56,8 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
@@ -192,7 +192,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
shadeInteractor =
ShadeInteractorImpl(
testScope.backgroundScope,
- FakeDeviceProvisioningRepository(),
+ kosmos.deviceProvisioningInteractor,
FakeDisableFlagsRepository(),
mock(),
keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d9ff22..7d99d05e3f0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@ import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3ac19a6..cac4a8d2d37f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.notification.footer.ui.view;
import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryShown() {
mView.showHistory(true);
assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void setHistoryNotShown() {
mView.showHistory(false);
assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@ public class FooterViewTest extends SysuiTestCase {
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonText(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getText().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonText(resId);
+ mView.setManageOrHistoryButtonText(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.manage_notifications_history_text;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+ int resId = R.string.manage_notifications_history_text;
+ mView.setManageOrHistoryButtonDescription(resId);
+ verify(mSpyContext).getString(eq(resId));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.manage_text))
+ .getContentDescription().toString()).contains("History");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setManageOrHistoryButtonDescription(resId);
+ mView.setManageOrHistoryButtonDescription(resId);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+ clearInvocations(mSpyContext);
+ int resId = R.string.accessibility_clear_all;
+ assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.clear_all_notifications_text;
mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@ public class FooterViewTest extends SysuiTestCase {
public void testSetClearAllButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.clear_all_notifications_text;
- assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonText(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -178,7 +239,7 @@ public class FooterViewTest extends SysuiTestCase {
public void testSetClearAllButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
- assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+ assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -206,7 +267,7 @@ public class FooterViewTest extends SysuiTestCase {
public void testSetMessageString_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.unlock_to_see_notif_text;
- assertLogsWtf(()-> mView.setMessageString(resId));
+ assertLogsWtf(() -> mView.setMessageString(resId));
verify(mSpyContext, never()).getString(anyInt());
}
@@ -231,7 +292,7 @@ public class FooterViewTest extends SysuiTestCase {
public void testSetMessageIcon_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.drawable.ic_friction_lock_closed;
- assertLogsWtf(()-> mView.setMessageIcon(resId));
+ assertLogsWtf(() -> mView.setMessageIcon(resId));
verify(mSpyContext, never()).getDrawable(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 8ab13f5aa720..620d97275949 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -14,109 +14,61 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class FooterViewModelTest : SysuiTestCase() {
- private lateinit var footerViewModel: FooterViewModel
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val configurationRepository: FakeConfigurationRepository
- val keyguardRepository: FakeKeyguardRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
-
- private val dozeParameters: DozeParameters = mock()
-
- private val testComponent: TestComponent =
- DaggerFooterViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParameters,
- )
- )
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val shadeRepository = kosmos.shadeRepository
+ private val powerRepository = kosmos.powerRepository
+ private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- // The underTest in the component is Optional, because that matches the provider we
- // currently have for the footer view model.
- footerViewModel = testComponent.underTest.get()
- }
+ val underTest = kosmos.footerViewModel
@Test
fun testMessageVisible_whenFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -125,8 +77,8 @@ class FooterViewModelTest : SysuiTestCase() {
@Test
fun testMessageVisible_whenNoFilteredNotifications() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.message.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.message.isVisible)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
@@ -135,8 +87,8 @@ class FooterViewModelTest : SysuiTestCase() {
@Test
fun testClearAllButtonVisible_whenHasClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -153,8 +105,8 @@ class FooterViewModelTest : SysuiTestCase() {
@Test
fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
activeNotificationListRepository.notifStats.value =
NotifStats(
@@ -171,12 +123,12 @@ class FooterViewModelTest : SysuiTestCase() {
@Test
fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is expanded
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(1f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@ class FooterViewModelTest : SysuiTestCase() {
@Test
fun testClearAllButtonAnimating_whenShadeNotExpanded() =
- testComponent.runTest {
- val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
// WHEN shade is collapsed
- keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
shadeRepository.setLegacyShadeExpansion(0f)
// AND QS not expanded
shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@ class FooterViewModelTest : SysuiTestCase() {
// THEN button visibility should not animate
assertThat(visible?.isAnimating).isFalse()
}
+
+ @Test
+ fun testManageButton_whenHistoryDisabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ // THEN label is "Manage"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+ }
+
+ @Test
+ fun testHistoryButton_whenHistoryEnabled() =
+ testScope.runTest {
+ val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ // THEN label is "History"
+ assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f290407..7589a49e1963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d34d98..88e4f5ab8d55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@ import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,46 +53,18 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
class NotificationListViewModelTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- ActivatableNotificationViewModelModule::class,
- FooterViewModelModule::class,
- HeadlessSystemUserModeModule::class,
- UnfoldTransitionModule.Bindings::class,
- NotificationStatsLoggerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<NotificationListViewModel> {
- val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val shadeRepository: FakeShadeRepository
- val zenModeRepository: FakeZenModeRepository
- val configurationController: FakeConfigurationController
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
- }
+ private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val fakeConfigurationController = kosmos.fakeConfigurationController
- private val testComponent: TestComponent =
- DaggerNotificationListViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
- },
- mocks = TestMocksModule()
- )
+ val underTest = kosmos.notificationListViewModel
@Before
fun setUp() {
@@ -110,11 +73,11 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testIsImportantForAccessibility_falseWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -129,11 +92,11 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testIsImportantForAccessibility_trueWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
testScope,
@@ -148,11 +111,11 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
- testComponent.runTest {
+ testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
// WHEN not on lockscreen
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
testScope,
@@ -167,7 +130,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
@@ -180,7 +143,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_falseWhenNotifs() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -193,13 +156,13 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.legacyQsFullscreen.value = true
+ fakeShadeRepository.legacyQsFullscreen.value = true
runCurrent()
// THEN should not show
@@ -208,16 +171,16 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- shadeRepository.setQsExpansion(1f)
+ fakeShadeRepository.setQsExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
- configurationController.notifyConfigurationChanged()
+ fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
// THEN should show
@@ -226,13 +189,13 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND transitioning to AOD
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
- testComponent.runTest {
+ testScope.runTest {
val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND is on bouncer
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
testScope,
@@ -267,7 +230,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testAreNotificationsHiddenInShade_true() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testAreNotificationsHiddenInShade_false() =
- testComponent.runTest {
+ testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testHasFilteredOutSeenNotifications_true() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
fun testHasFilteredOutSeenNotifications_false() =
- testComponent.runTest {
+ testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index cd5d5ed0d08e..9919c6b78aab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
@@ -23,7 +24,7 @@ import android.os.Handler
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import org.junit.Assert.assertFalse
@@ -69,6 +70,8 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ setShareFullScreen()
+
controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
// Obtain useful MediaProjectionCallback
@@ -195,6 +198,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
+ fun isSensitiveStateActive_projectionActive_singleActivity_false() {
+ setShareSingleApp()
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
fun shouldProtectNotification_projectionInactive_false() {
val notificationEntry = mock(NotificationEntry::class.java)
@@ -202,30 +213,74 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
- fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ fun shouldProtectNotification_projectionActive_singleActivity_false() {
+ setShareSingleApp()
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
- val notificationEntry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- val notification = mock(Notification::class.java)
- `when`(notificationEntry.sbn).thenReturn(sbn)
- `when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(true)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@Test
+ fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ private fun setShareFullScreen() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(null)
+ }
+
+ private fun setShareSingleApp() {
+ `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
+ `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ }
+
+ private fun setupNotificationEntry(
+ packageName: String,
+ isFgs: Boolean = false
+ ): NotificationEntry {
val notificationEntry = mock(NotificationEntry::class.java)
val sbn = mock(StatusBarNotification::class.java)
val notification = mock(Notification::class.java)
`when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.packageName).thenReturn(packageName)
`when`(sbn.notification).thenReturn(notification)
- `when`(notification.isFgsOrUij).thenReturn(false)
+ `when`(notification.isFgsOrUij).thenReturn(isFgs)
- assertTrue(controller.shouldProtectNotification(notificationEntry))
+ return notificationEntry
+ }
+
+ private fun setupFgsNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, /* isFgs= */ true)
+ }
+
+ companion object {
+ private const val TEST_PROJECTION_PACKAGE_NAME =
+ "com.android.systemui.statusbar.policy.projectionpackage"
+ private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 925ac2a2d067..9ea4142c9cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -494,7 +494,7 @@ public class BubblesTest extends SysuiTestCase {
mShadeInteractor =
new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
+ mKosmos.getDeviceProvisioningInteractor(),
new FakeDisableFlagsRepository(),
mDozeParameters,
keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
index 961022f0f426..a4f28f395a63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -20,4 +20,4 @@ package com.android.systemui.biometrics.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
+var Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
index 1c8bd3b58dfe..e9b7a69d1421 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -18,9 +18,12 @@
package com.android.systemui.biometrics.data.repository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeBiometricStatusRepository : BiometricStatusRepository {
private val _fingerprintAuthenticationReason =
@@ -28,7 +31,16 @@ class FakeBiometricStatusRepository : BiometricStatusRepository {
override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
_fingerprintAuthenticationReason.asStateFlow()
+ private val _fingerprintAcquiredStatus =
+ MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+ override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+ _fingerprintAcquiredStatus.asStateFlow().filterNotNull()
+
fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
_fingerprintAuthenticationReason.value = reason
}
+
+ fun setFingerprintAcquiredStatus(status: FingerprintAuthenticationStatus) {
+ _fingerprintAcquiredStatus.value = status
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ea3ccfb2909..1884a3264ed6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -28,7 +28,7 @@ class FakeCommunalMediaRepository : CommunalMediaRepository {
fun mediaActive(timestamp: Long = 0L) {
_mediaModel.value =
CommunalMediaModel(
- hasAnyMediaOrRecommendation = true,
+ hasActiveMediaOrRecommendation = true,
createdTimestampMillis = timestamp,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index fab64e38e1f8..7301404eada0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -36,7 +36,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
}
}
- override fun deleteWidget(widgetId: Int) {
+ override fun deleteWidgetFromDb(widgetId: Int) {
if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
return
}
@@ -44,6 +44,10 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
_communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
}
+ override fun deleteWidgetFromHost(widgetId: Int) {
+ deleteWidgetFromDb(widgetId)
+ }
+
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c818e9c8971c..c47f020a3b83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,15 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -39,6 +42,8 @@ val Kosmos.communalInteractor by Fixture {
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ logBuffer = logcatLogBuffer("CommunalInteractor"),
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index adaea7cbf64d..9776b436555d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.communal.data.repository.communalTutorialRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
val Kosmos.communalTutorialInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@ val Kosmos.communalTutorialInteractor by
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
communalInteractor = communalInteractor,
+ tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
new file mode 100644
index 000000000000..109e113611c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.controls.panels
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.authorizedPanelsRepository: AuthorizedPanelsRepository by
+ Kosmos.Fixture {
+ AuthorizedPanelsRepositoryImpl(applicationContext, fakeUserFileManager, fakeUserTracker)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
deleted file mode 100644
index a231212518ec..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.controls.panels
-
-import android.os.UserHandle
-import com.android.systemui.kosmos.Kosmos
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSelectedComponentRepository : SelectedComponentRepository {
- private var shouldAddDefaultPanel: Boolean = true
- private val _selectedComponentFlows =
- mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
- private var currentUserHandle: UserHandle = UserHandle.of(0)
-
- override fun selectedComponentFlow(
- userHandle: UserHandle
- ): Flow<SelectedComponentRepository.SelectedComponent?> {
- // Return an existing flow for the user or create a new one
- return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
- MutableStateFlow(null)
- }
- }
-
- override fun getSelectedComponent(
- userHandle: UserHandle
- ): SelectedComponentRepository.SelectedComponent? {
- return _selectedComponentFlows[getUserHandle(userHandle)]?.value
- }
-
- override fun setSelectedComponent(
- selectedComponent: SelectedComponentRepository.SelectedComponent
- ) {
- val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
- flow.value = selectedComponent
- }
-
- override fun removeSelectedComponent() {
- _selectedComponentFlows[currentUserHandle]?.value = null
- }
- override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
-
- override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
- shouldAddDefaultPanel = shouldAdd
- }
-
- fun setCurrentUserHandle(userHandle: UserHandle) {
- currentUserHandle = userHandle
- }
- private fun getUserHandle(userHandle: UserHandle): UserHandle {
- return if (userHandle == UserHandle.CURRENT) {
- currentUserHandle
- } else {
- userHandle
- }
- }
-}
-
-val Kosmos.selectedComponentRepository by
- Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
new file mode 100644
index 000000000000..ee5b7e560329
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.controls.panels
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.selectedComponentRepository: SelectedComponentRepository by
+ Kosmos.Fixture {
+ SelectedComponentRepositoryImpl(fakeUserFileManager, fakeUserTracker, testDispatcher)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 083de107c971..b9a3d383fc13 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -43,6 +43,8 @@ import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,6 +82,8 @@ class KosmosJavaAdapter(
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+ val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
+ val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 000000000000..9410ce6d657a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.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.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
new file mode 100644
index 000000000000..207c3f7711b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.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.settings
+
+import android.content.SharedPreferences
+import com.android.systemui.util.FakeSharedPreferences
+import java.io.File
+
+class FakeUserFileManager : UserFileManager {
+ private val sharedPreferences = mutableMapOf<SharedPrefKey, FakeSharedPreferences>()
+
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException("getFile not implemented in fake")
+ }
+
+ override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+ val key = SharedPrefKey(fileName, mode, userId)
+ return sharedPreferences.getOrPut(key) { FakeSharedPreferences() }
+ }
+
+ private data class SharedPrefKey(val fileName: String, val mode: Int, val userId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
new file mode 100644
index 000000000000..4d7a40ab3380
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
@@ -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 com.android.systemui.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserFileManager by Kosmos.Fixture { FakeUserFileManager() }
+var Kosmos.userFileManager: UserFileManager by Kosmos.Fixture { fakeUserFileManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index e13fa5207b33..82e0b8e83f24 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,6 +45,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.shadeControllerSceneImpl by
Kosmos.Fixture {
ShadeControllerSceneImpl(
+ mainDispatcher = testDispatcher,
scope = applicationCoroutineScope,
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index afd37b3f92dc..2bd76bec6852 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
@@ -63,7 +63,7 @@ val Kosmos.shadeInteractorImpl by
Kosmos.Fixture {
ShadeInteractorImpl(
scope = applicationCoroutineScope,
- deviceProvisioningRepository = deviceProvisioningRepository,
+ deviceProvisioningInteractor = deviceProvisioningInteractor,
disableFlagsRepository = disableFlagsRepository,
dozeParams = dozeParameters,
keyguardRepository = fakeKeyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca00a0a6..01cac4c1e030 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
activeNotificationsInteractor = activeNotificationsInteractor,
+ notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04de1540..489598c4dba9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@ val Kosmos.notificationListViewBinder by Fixture {
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
+ loggerOptional = Optional.of(notificationStatsLogger),
metricsLogger = metricsLogger,
hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
- loggerOptional = Optional.of(notificationStatsLogger),
+ notificationActivityStarter = { notificationActivityStarter },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e61de4d..d4e9bfbd1500 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+ Kosmos.Fixture { fakeConfigurationController }
val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 300229954044..fc6a800e186c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
@SysUISingleton
class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
- private val _isDeviceProvisioned = MutableStateFlow(false)
+ private val _isDeviceProvisioned = MutableStateFlow(true)
override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
private val _isFactoryResetProtectionActive = MutableStateFlow(false)
override val isFactoryResetProtectionActive: Flow<Boolean> = _isFactoryResetProtectionActive
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
new file mode 100644
index 000000000000..84bd3e8ff08d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+
+val Kosmos.deviceProvisioningInteractor by Fixture {
+ DeviceProvisioningInteractor(
+ repository = deviceProvisioningRepository,
+ )
+}
diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md
deleted file mode 100644
index 6adb61441bb1..000000000000
--- a/ravenwood/README-ravenwood+mockito.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Ravenwood and Mockito
-
-Last update: 2023-11-13
-
-- As of 2023-11-13, `external/mockito` is based on version 2.x.
-- Mockito didn't support static mocking before 3.4.0.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-- Latest Mockito is 5.*. According to https://github.com/mockito/mockito:
- `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.`
-
-- Mockito now supports Android natively.
- See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1
- - But it's unclear at this point to omakoto@ how the `mockito-android` module is built.
-
-- Potential plan:
- - Ideal option:
- - If we can update `external/mockito`, that'd be great, but it may not work because
- Mockito has removed the deprecated APIs.
- - Second option:
- - Import the latest mockito as `external/mockito-new`, and require ravenwood
- to use this one.
- - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests.
- - This probably will require the latest `bytebuddy` and `objenesis`. \ No newline at end of file
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 36d398cc160c..83fda9e72c3c 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -34,6 +34,8 @@ import sys
re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+DRY_RUN = "-n" in sys.argv
+
ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
@@ -46,7 +48,7 @@ stats_total = collections.defaultdict(int)
stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
stats_method = collections.defaultdict()
-with open(sys.argv[1]) as f:
+with open(sys.argv[-1]) as f:
for line in f.readlines():
result = re_result.search(line)
if result:
@@ -67,7 +69,7 @@ for clazz in stats_class.keys():
clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
for root, dirs, files in os.walk("."):
for f in files:
- if clazz_match.match(f):
+ if clazz_match.match(f) and not DRY_RUN:
path = os.path.join(root, f)
subprocess.run(["sed", "-i", "-E", SED_ARG, path])
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3670459c0300..8af561fbd273 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,7 +16,9 @@
package android.platform.test.ravenwood;
+import android.app.ActivityManager;
import android.app.Instrumentation;
+import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
@@ -30,10 +32,12 @@ import org.junit.runner.Description;
import java.io.PrintStream;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -50,11 +54,27 @@ public class RavenwoodRuleImpl {
private static ScheduledFuture<?> sPendingTimeout;
+ /**
+ * When set, an unhandled exception was discovered (typically on a background thread), and we
+ * capture it here to ensure it's reported as a test failure.
+ */
+ private static final AtomicReference<Throwable> sPendingUncaughtException =
+ new AtomicReference<>();
+
+ private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
+ (thread, throwable) -> {
+ // Remember the first exception we discover
+ sPendingUncaughtException.compareAndSet(null, throwable);
+ };
+
public static boolean isOnRavenwood() {
return true;
}
public static void init(RavenwoodRule rule) {
+ maybeThrowPendingUncaughtException(false);
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+
RuntimeInit.redirectLogStreams();
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
@@ -64,6 +84,8 @@ public class RavenwoodRuleImpl {
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
+
com.android.server.LocalServices.removeAllServicesForTest();
if (rule.mProvideMainThread) {
@@ -78,6 +100,10 @@ public class RavenwoodRuleImpl {
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
+
+ // Touch some references early to ensure they're <clinit>'ed
+ Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.VERSION.SDK);
}
public static void reset(RavenwoodRule rule) {
@@ -94,9 +120,13 @@ public class RavenwoodRuleImpl {
com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
+
+ maybeThrowPendingUncaughtException(true);
}
public static void logTestRunner(String label, Description description) {
@@ -120,4 +150,21 @@ public class RavenwoodRuleImpl {
}
out.println("-----END ALL THREAD STACKS-----");
}
+
+ /**
+ * If there's a pending uncaught exception, consume and throw it now. Typically used to
+ * report an exception on a background thread as a failure for the currently running test.
+ */
+ private static void maybeThrowPendingUncaughtException(boolean duringReset) {
+ final Throwable pending = sPendingUncaughtException.getAndSet(null);
+ if (pending != null) {
+ if (duringReset) {
+ throw new IllegalStateException(
+ "Found an uncaught exception during this test", pending);
+ } else {
+ throw new IllegalStateException(
+ "Found an uncaught exception before this test started", pending);
+ }
+ }
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b386ed13..daed457fb695 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,10 @@
package android.platform.test.ravenwood;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static org.junit.Assert.fail;
import android.platform.test.annotations.DisabledOnRavenwood;
@@ -94,12 +98,12 @@ public class RavenwoodRule implements TestRule {
}
}
- private static final int SYSTEM_UID = 1000;
private static final int NOBODY_UID = 9999;
- private static final int FIRST_APPLICATION_UID = 10000;
private static final AtomicInteger sNextPid = new AtomicInteger(100);
+ int mCurrentUser = USER_SYSTEM;
+
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
* tests its own unique PID.
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/list-ravenwood-tests.sh
new file mode 100755
index 000000000000..fb9b823ee93b
--- /dev/null
+++ b/ravenwood/list-ravenwood-tests.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# 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.
+
+# List all the ravenwood test modules.
+
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index 4135022d2bc9..a74bca47f692 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -7,16 +7,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-// Ravenwood tests run on the hostside, so we need mockit of the host variant.
-// But we need to use it in modules of the android variant, so we "wash" the variant with it.
-java_host_for_device {
- name: "mockito_ravenwood",
- libs: [
- "mockito",
- "objenesis",
- ],
-}
-
android_ravenwood_test {
name: "RavenwoodMockitoTest",
@@ -26,8 +16,6 @@ android_ravenwood_test {
static_libs: [
"junit",
"truth",
-
- "mockito_ravenwood",
],
libs: [
"android.test.mock",
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 364a86a0ec5f..1284d64b9a90 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
+import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
import org.junit.Rule;
@@ -78,4 +79,13 @@ public class RavenwoodMockitoTest {
assertThat(object.getPackageName()).isEqualTo("android");
}
+
+ @Test
+ public void testMockFinalClass() {
+ var object = mock(Parcel.class);
+
+ when(object.readInt()).thenReturn(123);
+
+ assertThat(object.readInt()).isEqualTo(123);
+ }
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e49b64ebdbef..a5ecd20b8b4f 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -82,6 +82,8 @@ android.os.UidBatteryConsumer
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
android.os.UserManager
+android.os.VibrationAttributes
+android.os.VibrationAttributes$Builder
android.os.WorkSource
android.content.ClipData
@@ -144,6 +146,7 @@ android.graphics.RectF
android.content.ContentProvider
+android.app.ActivityManager
android.app.Instrumentation
android.metrics.LogMaker
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
new file mode 100755
index 000000000000..3f4b8a79e864
--- /dev/null
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# 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.
+
+# Run all the ravenwood tests.
+
+# "echo" is to remove the newlines
+all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) )
+
+echo "Running tests: $all_tests"
+atest $all_tests
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 63784ba61150..44144f84f4e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5725,10 +5725,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
- public void attachAccessibilityOverlayToDisplay_enforcePermission(
+ public void attachAccessibilityOverlayToDisplay(
int displayId, SurfaceControl sc) {
mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+ INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 727721d5319f..532db126bff2 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "autofill"
description: "Guards against Autofill-Credman integration phase 2"
bug: "320730001"
-} \ No newline at end of file
+}
+
+flag {
+ name: "autofill_credman_dev_integration"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman Phase1 developer integration via new APIs"
+ bug: "320730001"
+}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be338a031..82d9377031bc 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@ final class UiModeManagerService extends SystemService {
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+ private final NightMode mNightMode = new NightMode(){
+ private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+ @Override
+ public int get() {
+ return mNightModeValue;
+ }
+
+ @Override
+ public void set(int mode) {
+ mNightModeValue = mode;
+ if (enableNightModeCache()) {
+ UiModeManager.invalidateNightModeCache();
+ }
+ }
+ };
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@ final class UiModeManagerService extends SystemService {
@Override
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+ if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -392,7 +409,7 @@ final class UiModeManagerService extends SystemService {
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
+ mNightMode.get(), 0);
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
@@ -412,7 +429,7 @@ final class UiModeManagerService extends SystemService {
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
- if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+ if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
verifySetupWizardCompleted();
synchronized (mLock) {
@@ -471,12 +488,12 @@ final class UiModeManagerService extends SystemService {
verifySetupWizardCompleted();
final Resources res = context.getResources();
+ mNightMode.set(res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode));
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
- mNightMode = res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@ final class UiModeManagerService extends SystemService {
private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
persistComputedNightMode(mCurrentUser);
}
}
@@ -585,7 +602,7 @@ final class UiModeManagerService extends SystemService {
}
private void updateCustomTimeLocked() {
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
@@ -606,9 +623,9 @@ final class UiModeManagerService extends SystemService {
return;
}
if (mSetupWizardComplete) {
- mNightMode = Secure.getIntForUser(context.getContentResolver(),
+ mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, res.getInteger(
- com.android.internal.R.integer.config_defaultNightMode), userId);
+ com.android.internal.R.integer.config_defaultNightMode), userId));
mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@ final class UiModeManagerService extends SystemService {
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
}
@@ -834,21 +851,23 @@ final class UiModeManagerService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (mNightMode != mode || mNightModeCustomType != customModeType) {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
cancelCustomAlarm();
}
mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
- mNightMode = mode;
+ mNightMode.set(mode);
//deactivates AttentionMode if user toggles DarkTheme
mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
- if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+ if ((mNightMode.get() != MODE_NIGHT_AUTO
+ && mNightMode.get() != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -865,7 +884,7 @@ final class UiModeManagerService extends SystemService {
@Override
public int getNightMode() {
synchronized (mLock) {
- return mNightMode;
+ return mNightMode.get();
}
}
@@ -999,18 +1018,19 @@ final class UiModeManagerService extends SystemService {
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO
+ || mNightMode.get() == MODE_NIGHT_CUSTOM) {
unregisterDeviceInactiveListenerLocked();
mOverrideNightModeOff = !active;
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
&& active) {
- mNightMode = UiModeManager.MODE_NIGHT_YES;
- } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+ mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
&& !active) {
- mNightMode = UiModeManager.MODE_NIGHT_NO;
+ mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
updateConfigurationLocked();
applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@ final class UiModeManagerService extends SystemService {
private void onCustomTimeUpdated(int user) {
persistNightMode(user);
- if (mNightMode != MODE_NIGHT_CUSTOM) return;
+ if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
unregisterDeviceInactiveListenerLocked();
updateLocked(0, 0);
@@ -1431,8 +1451,8 @@ final class UiModeManagerService extends SystemService {
pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
- pw.print(" mNightMode="); pw.print(mNightMode); pw.print(" (");
- pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+ pw.print(" mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+ pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@ final class UiModeManagerService extends SystemService {
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.UI_NIGHT_MODE, mNightMode.get(), user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@ final class UiModeManagerService extends SystemService {
uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
}
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+ if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+ updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
}
- if (mNightMode == MODE_NIGHT_AUTO) {
+ if (mNightMode.get() == MODE_NIGHT_AUTO) {
boolean activateNightMode = mComputedNightMode;
if (mTwilightManager != null) {
mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@ final class UiModeManagerService extends SystemService {
}
}
- if (mNightMode == MODE_NIGHT_CUSTOM) {
+ if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
} else {
@@ -2010,7 +2030,7 @@ final class UiModeManagerService extends SystemService {
private void updateComputedNightModeLocked(boolean activate) {
boolean newComputedValue = activate;
- if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
if (mOverrideNightModeOn && !newComputedValue) {
newComputedValue = true;
} else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@ final class UiModeManagerService extends SystemService {
mComputedNightMode = newComputedValue;
}
- if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+ if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
}
@@ -2279,4 +2299,13 @@ final class UiModeManagerService extends SystemService {
Sandman.startDreamWhenDockedIfAppropriate(context);
}
}
+
+ /**
+ * Interface to contain the value for system night mode. We make the night mode accessible
+ * through this class to ensure that the reassignment of this value invalidates the cache.
+ */
+ private interface NightMode {
+ int get();
+ void set(int mode);
+ }
}
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed12afe..856a15f0ecda 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@ public class AssistDataRequester extends IAssistDataReceiver.Stub {
// Ensure that the current activity supports assist data
boolean isAssistDataAllowed = false;
try {
- isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+ isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
} catch (RemoteException e) {
// Should never happen
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b521e89..f1eea728bedd 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@ public class AudioDeviceBroker {
return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mAudioService.isSADevice(deviceState);
+ }
+
//------------------------------------------------
// for testing purposes only
void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a9349a..102a960d35f6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@ public class AudioDeviceInventory {
&& di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@ public class AudioDeviceInventory {
&& di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
continue;
}
- ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice)
+ == mDeviceBroker.isSADevice(ads2)) {
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@ public class AudioDeviceInventory {
|| !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
continue;
}
-
- ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
- ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
- ads.setSAEnabled(updatedDevice.isSAEnabled());
+ if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+ ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads.setSAEnabled(updatedDevice.isSAEnabled());
+ }
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e0af35..bbbba26303a7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@ public class AudioService extends IAudioService.Stub
sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
- + " -> " + newDevice));
+ + " -> " + newDevice).printLog(TAG));
AudioDeviceBroker.BtDeviceChangedData data =
new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
"AudioService");
@@ -10638,6 +10638,10 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+ return mSpatializerHelper.isSADevice(deviceState);
+ }
+
private boolean isBluetoothPrividged() {
if (!bluetoothMacAddressAnonymization()) {
return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f127e77d..8d767313d5ab 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@ public class SpatializerHelper {
return false;
}
- private boolean isSADevice(AdiDeviceState deviceState) {
+ /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 1ae4d6465c57..1dc882e5ed2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -18,6 +18,8 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.hardware.biometrics.AuthenticationStateListener;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -115,7 +117,7 @@ public class AuthenticationStateListeners implements IBinder.DeathRecipient {
* @param userId The user Id for the requested authentication
*/
public void onAuthenticationFailed(int requestReason, int userId) {
- for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ for (AuthenticationStateListener listener : mAuthenticationStateListeners) {
try {
listener.onAuthenticationFailed(requestReason, userId);
} catch (RemoteException e) {
@@ -125,6 +127,27 @@ public class AuthenticationStateListeners implements IBinder.DeathRecipient {
}
}
+ /**
+ * Defines behavior in response to biometric being acquired.
+ * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+ * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+ * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+ * a known acquired message.
+ */
+ public void onAuthenticationAcquired(
+ BiometricSourceType biometricSourceType, int requestReason,
+ @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
+ ) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationAcquired(biometricSourceType, requestReason, acquiredInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "stopped", e);
+ }
+ }
+ }
+
@Override
public void binderDied() {
// Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index e0fd44b9f6bb..8121a639ab0a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -29,6 +29,7 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -102,6 +103,7 @@ public class FingerprintAuthenticationClient
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
+
public FingerprintAuthenticationClient(
@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@@ -280,6 +282,8 @@ public class FingerprintAuthenticationClient
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
// for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
+ mAuthenticationStateListeners.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
super.onAcquired(acquiredInfo, vendorCode);
PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 60c532c26f5d..b6311afb5ea1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -201,6 +202,8 @@ class FingerprintAuthenticationClient
@Override
public void onAcquired(int acquiredInfo, int vendorCode) {
+ mAuthenticationStateListeners.onAuthenticationAcquired(
+ BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
super.onAcquired(acquiredInfo, vendorCode);
@LockoutTracker.LockoutMode final int lockoutMode =
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93addcd84d12..e930627b55af 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -872,14 +872,15 @@ public final class DisplayManagerService extends SystemService {
}
@VisibleForTesting
- void performTraversalInternal(SurfaceControl.Transaction t) {
+ void performTraversalInternal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
synchronized (mSyncRoot) {
if (!mPendingTraversal) {
return;
}
mPendingTraversal = false;
- performTraversalLocked(t);
+ performTraversalLocked(t, displayTransactions);
}
// List is self-synchronized copy-on-write.
@@ -2593,7 +2594,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void performTraversalLocked(SurfaceControl.Transaction t) {
+ private void performTraversalLocked(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
// Clear all viewports before configuring displays so that we can keep
// track of which ones we have configured.
clearViewportsLocked();
@@ -2601,9 +2603,11 @@ public final class DisplayManagerService extends SystemService {
// Configure each display device.
mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final SurfaceControl.Transaction displayTransaction =
+ displayTransactions.get(display.getDisplayIdLocked(), t);
if (device != null) {
- configureDisplayLocked(t, device);
- device.performTraversalLocked(t);
+ configureDisplayLocked(displayTransaction, device);
+ device.performTraversalLocked(displayTransaction);
}
});
@@ -4680,8 +4684,9 @@ public final class DisplayManagerService extends SystemService {
}
@Override
- public void performTraversal(SurfaceControl.Transaction t) {
- performTraversalInternal(t);
+ public void performTraversal(SurfaceControl.Transaction t,
+ SparseArray<SurfaceControl.Transaction> displayTransactions) {
+ performTraversalInternal(t, displayTransactions);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f031b7b677ac..cb00e44f09a9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3185,6 +3185,24 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -3659,6 +3677,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
}
+ // The user represented by userId, must be running.
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.w(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3666,20 +3693,67 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ // If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third arty code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
getSelectedMethodIdLocked(), getSequenceNumberLocked(),
false /* isInputMethodSuppressingSpellChecker */);
}
+ final ClientState cs = mClientController.getClient(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("Unknown client " + client.asBinder());
+ }
final long ident = Binder.clearCallingIdentity();
try {
+ // Verify if IMMS is in the process of switching user.
+ if (mUserSwitchHandlerTask != null) {
+ // There is already an on-going pending user switch task.
+ final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+ if (userId == nextUserId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
+ mSettings.getUserId(), false /* enabledOnly */);
+ for (int profileId : profileIdsWithDisabled) {
+ if (profileId == userId) {
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ }
+ return InputBindResult.INVALID_USER;
+ }
+
+ // Ensure that caller's focused window and display parameters are allowd to
+ // display input method.
+ final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+ windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
+ switch (imeClientFocus) {
+ case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
+ Slog.e(TAG,
+ "startInputOrWindowGainedFocusInternal: display ID mismatch.");
+ return InputBindResult.DISPLAY_ID_MISMATCH;
+ case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
+ // Check with the window manager to make sure this client actually
+ // has a window with focus. If not, reject. This is thread safe
+ // because if the focus changes some time before or after, the
+ // next client receiving focus that has any interest in input will
+ // be calling through here after that change happens.
+ if (DEBUG) {
+ Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
+ + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
+ }
+ return InputBindResult.NOT_IME_TARGET_WINDOW;
+ case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
+ return InputBindResult.INVALID_DISPLAY_ID;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3708,7 +3782,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3721,60 +3795,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ " userId=" + userId
- + " imeDispatcher=" + imeDispatcher);
- }
-
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.w(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
-
- final ClientState cs = mClientController.getClient(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException("Unknown client " + client.asBinder());
- }
-
- final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
- windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
- switch (imeClientFocus) {
- case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
- Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
- return InputBindResult.DISPLAY_ID_MISMATCH;
- case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
- // Check with the window manager to make sure this client actually
- // has a window with focus. If not, reject. This is thread safe
- // because if the focus changes some time before or after, the
- // next client receiving focus that has any interest in input will
- // be calling through here after that change happens.
- if (DEBUG) {
- Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
- + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
- }
- return InputBindResult.NOT_IME_TARGET_WINDOW;
- case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
- return InputBindResult.INVALID_DISPLAY_ID;
- }
-
- if (mUserSwitchHandlerTask != null) {
- // There is already an on-going pending user switch task.
- final int nextUserId = mUserSwitchHandlerTask.mToUserId;
- if (userId == nextUserId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
- for (int profileId : profileIdsWithDisabled) {
- if (profileId == userId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- }
- return InputBindResult.INVALID_USER;
+ + " imeDispatcher=" + imeDispatcher
+ + " cs=" + cs);
}
final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
@@ -5366,7 +5388,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ final String imeId = imi != null ? imi.getId() : "";
+ mSettings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ mSettings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
}
}
@@ -5509,6 +5535,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
@@ -6555,6 +6584,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ settings.putSelectedDefaultDeviceInputMethod(nextIme);
+ }
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002be344f..e444db1b79e8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@ final class InputMethodSettings {
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9bbe20..1c9bbab3969e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2049,7 +2049,9 @@ public class NotificationManagerService extends SystemService {
if (!mUserProfiles.isProfileUser(userId)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
- mZenModeHelper.onUserUnlocked(userId);
+ if (!android.app.Flags.modesApi()) {
+ mZenModeHelper.onUserUnlocked(userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 41ff4150eb23..2f20bbe7f29c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -328,6 +328,7 @@ public class ZenModeHelper {
}
}
+ // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
public void onUserUnlocked(int user) {
loadConfigForUser(user, "onUserUnlocked");
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dbff4423d10c..722654a9102c 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@ flag {
}
flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
name: "sensitive_notification_app_protection"
namespace: "systemui"
description: "This flag controls the sensitive notification app protections while screen sharing"
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 86d05d92c95b..25a39cc8456f 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@ final class IdmapManager {
private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
@NonNull AndroidPackage overlayPackage, int userId) {
String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
- if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
+ if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index a61b03fdbb39..b9464d96a019 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,7 +32,6 @@ import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
-
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
@@ -363,7 +362,7 @@ public final class OverlayManagerService extends SystemService {
defaultPackages.add(packageName);
}
}
- return defaultPackages.toArray(new String[0]);
+ return defaultPackages.toArray(new String[defaultPackages.size()]);
}
private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1144,10 +1143,9 @@ public final class OverlayManagerService extends SystemService {
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
- private static final class PackageStateUsers {
+ private static class PackageStateUsers {
private PackageState mPackageState;
- private Boolean mDefinesOverlayable = null;
- private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
+ private final Set<Integer> mInstalledUsers = new ArraySet<>();
private PackageStateUsers(@NonNull PackageState packageState) {
this.mPackageState = packageState;
}
@@ -1162,7 +1160,7 @@ public final class OverlayManagerService extends SystemService {
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
- private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
+ private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1178,7 +1176,8 @@ public final class OverlayManagerService extends SystemService {
*/
@NonNull
public ArrayMap<String, PackageState> initializeForUser(final int userId) {
- if (mInitializedUsers.add(userId)) {
+ if (!mInitializedUsers.contains(userId)) {
+ mInitializedUsers.add(userId);
mPackageManagerInternal.forEachPackageState((packageState -> {
if (packageState.getPkg() != null
&& packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1197,11 +1196,13 @@ public final class OverlayManagerService extends SystemService {
return userPackages;
}
- private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
+ @Override
+ @Nullable
+ public PackageState getPackageStateForUser(@NonNull final String packageName,
final int userId) {
final PackageStateUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
- return pkg;
+ return pkg.mPackageState;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1215,14 +1216,8 @@ public final class OverlayManagerService extends SystemService {
return addPackageUser(packageName, userId);
}
- @Override
- public PackageState getPackageStateForUser(@NonNull final String packageName,
- final int userId) {
- final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
- return pkg != null ? pkg.mPackageState : null;
- }
-
- private PackageStateUsers addPackageUser(@NonNull final String packageName,
+ @NonNull
+ private PackageState addPackageUser(@NonNull final String packageName,
final int user) {
final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
if (pkg == null) {
@@ -1234,20 +1229,20 @@ public final class OverlayManagerService extends SystemService {
}
@NonNull
- private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
+ private PackageState addPackageUser(@NonNull final PackageState pkg,
final int user) {
PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new PackageStateUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
- } else if (pkgUsers.mPackageState != pkg) {
+ } else {
pkgUsers.mPackageState = pkg;
- pkgUsers.mDefinesOverlayable = null;
}
pkgUsers.mInstalledUsers.add(user);
- return pkgUsers;
+ return pkgUsers.mPackageState;
}
+
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1265,15 +1260,15 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @Nullable
public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
+ @Nullable
public PackageState onPackageUpdated(@NonNull final String packageName,
final int userId) {
- final var pu = addPackageUser(packageName, userId);
- return pu != null ? pu.mPackageState : null;
+ return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1313,30 +1308,22 @@ public final class OverlayManagerService extends SystemService {
return (pkgs.length == 0) ? null : pkgs[0];
}
+ @Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(packageName, userId);
- final var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(packageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
- return null;
- }
-
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- if (psu.mDefinesOverlayable == null) {
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- }
- return Boolean.FALSE.equals(psu.mDefinesOverlayable)
- ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
+ return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
@@ -1350,29 +1337,24 @@ public final class OverlayManagerService extends SystemService {
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- final var psu = getRawPackageStateForUser(targetPackageName, userId);
- var pkg = (psu == null || psu.mPackageState == null)
- ? null : psu.mPackageState.getAndroidPackage();
+ var packageState = getPackageStateForUser(targetPackageName, userId);
+ var pkg = packageState == null ? null : packageState.getAndroidPackage();
if (pkg == null) {
throw new IOException("Unable to get target package");
}
- if (psu.mDefinesOverlayable == null) {
- ApkAssets apkAssets = null;
- try {
- apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
- ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
- psu.mDefinesOverlayable = apkAssets.definesOverlayable();
- } finally {
- if (apkAssets != null) {
- try {
- apkAssets.close();
- } catch (Throwable ignored) {
- }
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+ return apkAssets.definesOverlayable();
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
}
}
}
- return psu.mDefinesOverlayable;
}
@Override
@@ -1563,7 +1545,8 @@ public final class OverlayManagerService extends SystemService {
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId, false);
for (final String targetPackageName : targetPackageNames) {
- final var list = new OverlayPaths.Builder(frameworkOverlays);
+ final OverlayPaths.Builder list = new OverlayPaths.Builder();
+ list.addAll(frameworkOverlays);
if (!"android".equals(targetPackageName)) {
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
}
@@ -1575,21 +1558,17 @@ public final class OverlayManagerService extends SystemService {
final HashSet<String> invalidPackages = new HashSet<>();
pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
- if (DEBUG || !invalidPackages.isEmpty()) {
- for (final String targetPackageName : targetPackageNames) {
- if (DEBUG) {
- Slog.d(TAG,
- "-> Updating overlay: target=" + targetPackageName + " overlays=["
- + pendingChanges.get(targetPackageName)
- + "] userId=" + userId);
- }
+ for (final String targetPackageName : targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ + pendingChanges.get(targetPackageName)
+ + "] userId=" + userId);
+ }
- if (invalidPackages.contains(targetPackageName)) {
- Slog.e(TAG, TextUtils.formatSimple(
- "Failed to change enabled overlays for %s user %d",
- targetPackageName,
- userId));
- }
+ if (invalidPackages.contains(targetPackageName)) {
+ Slog.e(TAG, TextUtils.formatSimple(
+ "Failed to change enabled overlays for %s user %d", targetPackageName,
+ userId));
}
}
return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c1b6ccc7e25c..972c78db9460 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,20 +772,24 @@ final class OverlayManagerServiceImpl {
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
final int userId, boolean includeImmutableOverlays) {
- final var paths = new OverlayPaths.Builder();
- mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
+ userId);
+ final OverlayPaths.Builder paths = new OverlayPaths.Builder();
+ final int n = overlays.size();
+ for (int i = 0; i < n; i++) {
+ final OverlayInfo oi = overlays.get(i);
if (!oi.isEnabled()) {
- return;
+ continue;
}
if (!includeImmutableOverlays && !oi.isMutable) {
- return;
+ continue;
}
if (oi.isFabricated()) {
paths.addNonApkPath(oi.baseCodePath);
} else {
paths.addApkPath(oi.baseCodePath);
}
- });
+ }
return paths.build();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b8b49f3eed2f..eae614ac9e77 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,7 +47,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -183,23 +182,6 @@ final class OverlayManagerSettings {
return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
- void forEachMatching(int userId, String overlayName, String targetPackageName,
- @NonNull Consumer<OverlayInfo> consumer) {
- for (int i = 0, n = mItems.size(); i < n; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.getUserId() != userId) {
- continue;
- }
- if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
- continue;
- }
- if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
- continue;
- }
- consumer.accept(item.getOverlayInfo());
- }
- }
-
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
final List<SettingsItem> items = selectWhereUser(userId);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 474b5907524c..339b1e71c091 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -19,7 +19,6 @@ package com.android.server.pm;
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
-import static android.app.ActivityManager.START_SUCCESS;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
@@ -286,10 +285,16 @@ public class PackageArchiver {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
t.getLocalizedMessage()));
- return START_ABORTED;
}
- return START_SUCCESS;
+ // We return STATUS_ABORTED because:
+ // 1. Archived App is not actually present during activity start. Hence the unarchival
+ // start should be treated as an error code.
+ // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user
+ // experience.
+ // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like
+ // aborting activity options, animations etc in the Windows Manager.
+ return START_ABORTED;
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51790b875465..b947aa38671f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5016,6 +5016,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+ Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+ + mStylusButtonsEnabled);
if (mStylusButtonsEnabled) {
sendSystemKeyToStatusBarAsync(event);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957f46b8..37f38252698e 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@ public class WallpaperCropper {
for (Rect crop : relativeCropHints) {
Rect originalRect = new Rect(crop);
originalRect.scale(wallpaper.mSampleSize);
- originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
result.add(originalRect);
}
return result;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6f52b89819e..85580ac6810a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1034,20 +1034,19 @@ class ActivityStarter {
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+ // We couldn't find the specific class specified in the Intent.
+ err = ActivityManager.START_CLASS_NOT_FOUND;
+
if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
- return packageArchiver
+ err = packageArchiver
.requestUnarchiveOnActivityStart(
intent, callingPackage, mRequest.userId, realCallingUid);
}
}
-
- // We couldn't find the specific class specified in the Intent.
- // Also the end of the line.
- err = ActivityManager.START_CLASS_NOT_FOUND;
}
if (err == ActivityManager.START_SUCCESS && sourceRecord != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3dd2290..0def5a1861ea 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public boolean isAssistDataAllowedOnCurrentActivity() {
+ public boolean isAssistDataAllowed() {
int userId;
+ boolean hasRestrictedWindow;
synchronized (mGlobalLock) {
final Task focusedRootTask = getTopDisplayFocusedRootTask();
if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return false;
}
userId = activity.mUserId;
+ DisplayContent displayContent = activity.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+ return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+ getUserManager().getProfileType(windowState.mShowUserId));
+ }, true /* traverseTopToBottom */);
}
- return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+ return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+ && !hasRestrictedWindow;
}
private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82dbf8d4242b..716aee3f8692 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1429,14 +1429,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mTokenMap.get(binder);
}
- ActivityRecord getActivityRecord(IBinder binder) {
- final WindowToken token = getWindowToken(binder);
- if (token == null) {
- return null;
- }
- return token.asActivityRecord();
- }
-
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
@@ -2250,7 +2242,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- mWmService.mDisplayManagerInternal.performTraversal(transaction);
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
@@ -6985,7 +6976,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord r = getActivityRecord(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
// Ignore the animating recents so the fixed rotation transform won't be switched twice
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951d0679..d08f0438f853 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
+import android.os.Trace;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@ class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+ try {
+ doLogSentTransition(transition, targets);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogSentTransition(
+ Transition transition, ArrayList<Transition.ChangeInfo> targets) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -91,6 +102,15 @@ class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+ try {
+ doLogFinishTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogFinishTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -114,6 +134,15 @@ class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+ try {
+ doLogAbortedTransition(transition);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAbortedTransition(Transition transition) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
@@ -131,6 +160,15 @@ class PerfettoTransitionTracer implements TransitionTracer {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+ try {
+ doLogRemovingStartingWindow(startingData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
mDataSource.trace((ctx) -> {
final ProtoOutputStream os = ctx.newTracePacket();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 587cc7489763..25646f19c461 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -249,6 +249,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+ private final SparseArray<SurfaceControl.Transaction> mDisplayTransactions =
+ new SparseArray<>();
/** The current user */
int mCurrentUser;
@@ -569,22 +571,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}, true /* traverseTopToBottom */);
}
- /**
- * Returns the app window token for the input binder if it exist in the system.
- * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
- * AppWindowToken represents an activity which can only exist on one display.
- */
- ActivityRecord getActivityRecord(IBinder binder) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayContent dc = mChildren.get(i);
- final ActivityRecord activity = dc.getActivityRecord(binder);
- if (activity != null) {
- return activity;
- }
- }
- return null;
- }
-
/** Returns the window token for the input binder if it exist in the system. */
WindowToken getWindowToken(IBinder binder) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -991,11 +977,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
+ mDisplayTransactions.append(dc.mDisplayId, dc.getSyncTransaction());
}
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(t);
+ mWmService.mDisplayManagerInternal.performTraversal(t, mDisplayTransactions);
+ mDisplayTransactions.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb45884887c..967f415d2c11 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
public class ScreenRecordingCallbackController {
@@ -57,10 +56,10 @@ public class ScreenRecordingCallbackController {
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+ private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
@GuardedBy("WindowManagerService.mGlobalLock")
- private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+ private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
private final WindowManagerService mWms;
@@ -108,8 +107,8 @@ public class ScreenRecordingCallbackController {
}
IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- IMediaProjectionManager mediaProjectionManager =
- IMediaProjectionManager.Stub.asInterface(binder);
+ IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+ binder);
long identityToken = Binder.clearCallingIdentity();
MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@ public class ScreenRecordingCallbackController {
synchronized (mWms.mGlobalLock) {
IBinder binder = callback.asBinder();
Callback callbackInfo = mCallbacks.remove(binder);
+ if (callbackInfo == null) {
+ return;
+ }
binder.unlinkToDeath(callbackInfo, 0);
boolean uidHasCallback = false;
- for (Callback cb : mCallbacks.values()) {
- if (cb.mUid == callbackInfo.mUid) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
uidHasCallback = true;
break;
}
@@ -213,7 +215,9 @@ public class ScreenRecordingCallbackController {
return;
}
- dispatchCallbacks(Set.of(uid), processVisible);
+ ArraySet<Integer> uidSet = new ArraySet<>();
+ uidSet.add(uid);
+ dispatchCallbacks(uidSet, processVisible);
}
@GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@ public class ScreenRecordingCallbackController {
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private Set<Integer> getRecordedUids() {
- Set<Integer> result = new ArraySet<>();
+ private ArraySet<Integer> getRecordedUids() {
+ ArraySet<Integer> result = new ArraySet<>();
if (mRecordedWC == null) {
return result;
}
@@ -248,37 +252,43 @@ public class ScreenRecordingCallbackController {
}
@GuardedBy("WindowManagerService.mGlobalLock")
- private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
if (uids.isEmpty()) {
return;
}
- for (Integer uid : uids) {
- mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ for (int i = 0; i < uids.size(); i++) {
+ mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
}
- for (Callback callback : mCallbacks.values()) {
- if (!uids.contains(callback.mUid)) {
- continue;
- }
- try {
- callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
- } catch (RemoteException e) {
- // Client has died. Cleanup is handled via DeathRecipient.
+ ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+ callbacks.add(mCallbacks.valueAt(i).mCallback);
}
}
+
+ mWms.mH.post(() -> {
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ });
}
void dump(PrintWriter pw) {
pw.format("ScreenRecordingCallbackController:\n");
pw.format(" Registered callbacks:\n");
- for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
- pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ pw.format(" callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
}
pw.format(" Last invoked states:\n");
- for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
- pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
- entry.getValue());
+ for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+ mLastInvokedStateByUid.valueAt(i));
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f9ed8353456..5b5177683252 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5070,7 +5070,7 @@ class Task extends TaskFragment {
mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
if (waitingActivity != null) {
- mWmService.setWindowOpaqueLocked(waitingActivity.token, false);
+ waitingActivity.setMainWindowOpaque(false);
if (waitingActivity.attachedToProcess()) {
try {
waitingActivity.app.getThread().scheduleTranslucentConversionComplete(
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b871be..debe7946dc95 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@ class TrustedOverlayHost {
void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
requireOverlaySurfaceControl();
- mOverlays.add(p);
+
+ boolean hasExistingOverlay = false;
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+ hasExistingOverlay = true;
+ }
+ }
+ if (!hasExistingOverlay) {
+ mOverlays.add(p);
+ }
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 426694d178af..554cbce510d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@ import static com.android.window.flags.Flags.multiCrop;
import android.Manifest;
import android.Manifest.permission;
import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -1073,7 +1074,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- final ActivityRecord atoken = mRoot.getActivityRecord(token);
+ final ActivityRecord atoken = ActivityRecord.forTokenLocked(token);
if (atoken == null) {
return;
}
@@ -3105,13 +3106,6 @@ public class WindowManagerService extends IWindowManager.Stub
return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
}
- void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
- final ActivityRecord wtoken = mRoot.getActivityRecord(token);
- if (wtoken != null) {
- wtoken.setMainWindowOpaque(isOpaque);
- }
- }
-
boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
aspectRatio);
@@ -3292,7 +3286,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+ @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
/**
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
@@ -4520,7 +4514,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public SurfaceControl addShellRoot(int displayId, IWindow client,
@WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4539,7 +4533,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setShellRootAccessibilityWindow(int displayId,
@WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4562,7 +4556,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void setDisplayWindowInsetsController(
int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4581,7 +4575,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+ @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void updateDisplayWindowRequestedVisibleTypes(
int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5841,7 +5835,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
setForcedDisplaySize_enforcePermission();
@@ -5859,7 +5853,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayScalingMode(int displayId, int mode) {
setForcedDisplayScalingMode_enforcePermission();
@@ -5947,7 +5941,7 @@ public class WindowManagerService extends IWindowManager.Stub
return changed;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplaySize(int displayId) {
clearForcedDisplaySize_enforcePermission();
@@ -6010,7 +6004,7 @@ public class WindowManagerService extends IWindowManager.Stub
return -1;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
setForcedDisplayDensityForUser_enforcePermission();
@@ -6036,7 +6030,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void clearForcedDisplayDensityForUser(int displayId, int userId) {
clearForcedDisplayDensityForUser_enforcePermission();
@@ -6536,7 +6530,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+ @EnforcePermission(android.Manifest.permission.STATUS_BAR)
public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
@@ -6578,7 +6572,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
@Override
public Region getCurrentImeTouchRegion() {
getCurrentImeTouchRegion_enforcePermission();
@@ -8468,12 +8462,13 @@ public class WindowManagerService extends IWindowManager.Stub
SurfaceControlViewHost.SurfacePackage overlay) {
if (overlay == null) {
throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
- } else if (overlay.getSurfaceControl() == null
- || !overlay.getSurfaceControl().isValid()) {
- throw new IllegalArgumentException(
- "Invalid overlay surfacecontrol passed in for task=" + taskId);
}
synchronized (mGlobalLock) {
+ if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -9956,13 +9951,17 @@ public class WindowManagerService extends IWindowManager.Stub
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ registerScreenRecordingCallback_enforcePermission();
return mScreenRecordingCallbackController.register(callback);
}
+ @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
@Override
public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ unregisterScreenRecordingCallback_enforcePermission();
mScreenRecordingCallbackController.unregister(callback);
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 56423b961813..897afbfd2046 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -73,7 +73,6 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"framework",
- "mockito_ravenwood",
"ravenwood-runtime",
"ravenwood-utils",
"services",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 75febd902dcf..42bcb3367933 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -103,6 +103,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -432,7 +433,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -507,7 +508,7 @@ public class DisplayManagerServiceTest {
assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
}
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -559,7 +560,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -594,7 +595,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -632,7 +633,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -667,7 +668,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
@@ -947,7 +948,7 @@ public class DisplayManagerServiceTest {
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1439,7 +1440,7 @@ public class DisplayManagerServiceTest {
PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1694,7 +1695,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
// flush the handler
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
@@ -1728,7 +1729,7 @@ public class DisplayManagerServiceTest {
null /* projection */, PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -1797,7 +1798,7 @@ public class DisplayManagerServiceTest {
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
- displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+ performTraversalInternal(displayManager);
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
@@ -2912,6 +2913,11 @@ public class DisplayManagerServiceTest {
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void performTraversalInternal(DisplayManagerService displayManager) {
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class),
+ new SparseArray<>());
+ }
+
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f49f6383b3c8..64fef68e387a 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -81,7 +81,6 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"truth",
- "mockito_ravenwood",
],
srcs: [
":power_stats_ravenwood_tests",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5611415c8fb1..ad6e2c657c96 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -153,7 +153,6 @@ android_ravenwood_test {
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "mockito_ravenwood",
"services.core",
],
srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f96d9e841eba..9cdaec647a43 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -18,6 +18,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -517,6 +518,16 @@ public class FingerprintAuthenticationClientTest {
}
@Test
+ public void testAuthenticationStateListeners_onAuthenticationAcquired()
+ throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAcquired(FINGERPRINT_ACQUIRED_START, 0);
+
+ verify(mAuthenticationStateListeners).onAuthenticationAcquired(any(), anyInt(), anyInt());
+ }
+
+ @Test
public void testAuthenticationStateListeners_onAuthenticationSucceeded()
throws RemoteException {
mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b0a0d2..ea84eb2fbf73 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,6 +49,7 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -118,6 +120,7 @@ public class BugreportManagerServiceImplTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@ public class BugreportManagerServiceImplTest {
@Test
@RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ @Ignore
public void testBugreportFileManagerKeepFilesOnRetrieval() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@ public class BugreportManagerServiceImplTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7ce4926..c247c08c8010 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -174,6 +175,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Before
+ public void disableProcessCaches() {
+ IpcDataCache.disableForTestMode();
+ }
+
+ @Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
mPermissionEnforcer = new FakePermissionEnforcer();
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 5d114f4bb702..74622014aa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5202,7 +5202,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// rules for a missing package, created a long time ago and deleted a long time ago
config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
- mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+ mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
.containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0ffe2d..c2a5824d4ffd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@ public class AssistDataRequesterTest {
private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
boolean assistScreenshotAllowed) throws Exception {
- doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+ doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
.noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 09f677e701df..46e14d51b5fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -38,13 +36,12 @@ import android.app.servertransaction.ClientTransactionItem;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -56,13 +53,8 @@ import org.mockito.MockitoAnnotations;
*/
@SmallTest
@Presubmit
-public class ClientLifecycleManagerTests {
-
- @Rule(order = 0)
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
- @Rule(order = 1)
- public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+@RunWith(WindowTestRunner.class)
+public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Mock
private IBinder mClientBinder;
@@ -86,7 +78,7 @@ public class ClientLifecycleManagerTests {
public void setup() {
MockitoAnnotations.initMocks(this);
- mWms = mSystemServices.getWindowManagerService();
+ mWms = mSystemServicesTestRule.getWindowManagerService();
mLifecycleManager = spy(new ClientLifecycleManager());
mLifecycleManager.setWindowManager(mWms);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be837441ef94..0ee78e38301c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1802,8 +1802,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
@Override
public void addStartingWindow(StartingWindowInfo info) {
synchronized (mWMService.mGlobalLock) {
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- info.appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1825,8 +1824,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
if (appToken != null) {
mTaskAppMap.remove(removalInfo.taskId);
- final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- appToken);
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(appToken);
WindowState win = mAppWindowMap.remove(appToken);
activity.removeChild(win);
activity.mStartingWindow = null;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 7ccc27e2f94e..0c324e6061cb 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -649,12 +649,19 @@ public final class NetworkRegistrationInfo implements Parcelable {
}
/**
- * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
- * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
- * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
- * @hide
+ * Get the 3GPP/3GPP2 reason code indicating why registration failed.
+ *
+ * Returns the reason code for non-transient registration failures. Typically this method will
+ * only return the last reason code received during a network selection procedure. The reason
+ * code is system-specific; however, the reason codes for both 3GPP and 3GPP2 systems are
+ * largely equivalent across generations.
+ *
+ * @return registration reject cause if available, otherwise 0. Depending on
+ * {@link #getAccessNetworkTechnology}, the values are defined in 3GPP TS 24.008 10.5.3.6 for
+ * WCDMA/UMTS, 3GPP TS 24.301 9.9.3.9 for LTE/EPS, 3GPP 24.501 Annex A for NR/5GS, or 3GPP2
+ * A.S0001 6.2.2.44 for CDMA.
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NETWORK_REGISTRATION_INFO_REJECT_CAUSE)
public int getRejectCause() {
return mRejectCause;
}