summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp2
-rw-r--r--Android.bp1
-rw-r--r--TEST_MAPPING1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/TEST_MAPPING3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING4
-rw-r--r--boot/Android.bp7
-rw-r--r--cmds/uinput/README.md1
-rw-r--r--core/api/current.txt8
-rw-r--r--core/api/system-current.txt34
-rw-r--r--core/java/android/app/GrammaticalInflectionManager.java53
-rw-r--r--core/java/android/app/IGrammaticalInflectionManager.aidl12
-rw-r--r--core/java/android/app/OWNERS3
-rw-r--r--core/java/android/app/TEST_MAPPING2
-rw-r--r--core/java/android/app/grammatical_inflection_manager.aconfig8
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java46
-rw-r--r--core/java/android/content/pm/TEST_MAPPING3
-rw-r--r--core/java/android/content/pm/flags.aconfig7
-rw-r--r--core/java/android/os/TEST_MAPPING3
-rw-r--r--core/java/android/os/UserManager.java12
-rw-r--r--core/java/android/service/notification/TEST_MAPPING6
-rw-r--r--core/java/android/text/TEST_MAPPING2
-rw-r--r--core/java/android/text/flags/fix_double_underline.aconfig8
-rw-r--r--core/java/android/view/View.java8
-rw-r--r--core/java/android/view/animation/AnimationUtils.java10
-rw-r--r--core/java/android/view/autofill/AutofillManager.java20
-rw-r--r--core/java/android/view/flags/variable_refresh_rate_flags.aconfig7
-rw-r--r--core/java/android/window/ITaskFragmentOrganizerController.aidl7
-rw-r--r--core/java/android/window/TaskFragmentOrganizer.java30
-rw-r--r--core/java/android/window/TaskFragmentTransaction.java28
-rw-r--r--core/java/com/android/internal/infra/TEST_MAPPING4
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl9
-rw-r--r--core/res/res/values/config_telephony.xml31
-rw-r--r--core/tests/coretests/src/android/graphics/drawable/IconTest.java158
-rw-r--r--core/tests/coretests/src/android/window/flags/WindowFlagsTest.java7
-rw-r--r--core/tests/vibrator/TEST_MAPPING1
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java46
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt5
-rw-r--r--libs/hwui/Android.bp4
-rw-r--r--libs/hwui/FeatureFlags.h40
-rw-r--r--libs/hwui/SkiaCanvas.cpp5
-rw-r--r--libs/hwui/hwui/Canvas.cpp110
-rw-r--r--libs/hwui/hwui/Canvas.h2
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h141
-rw-r--r--libs/hwui/jni/Gainmap.cpp3
-rw-r--r--libs/hwui/tests/unit/UnderlineTest.cpp153
-rw-r--r--location/Android.bp5
-rw-r--r--media/java/android/media/projection/TEST_MAPPING3
-rw-r--r--media/java/android/media/tv/TvView.java18
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java1
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/TEST_MAPPING2
-rw-r--r--packages/SystemUI/compose/core/Android.bp2
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt4
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt300
-rw-r--r--packages/SystemUI/res/layout/connected_display_dialog.xml4
-rw-r--r--packages/SystemUI/res/values-land/config.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/config.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt127
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt248
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt169
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt234
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt207
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt161
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt145
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt)28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt47
-rw-r--r--services/companion/java/com/android/server/companion/virtual/TEST_MAPPING2
-rw-r--r--services/core/java/com/android/server/display/TEST_MAPPING1
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java173
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java166
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/OWNERS1
-rw-r--r--services/core/java/com/android/server/lights/TEST_MAPPING6
-rw-r--r--services/core/java/com/android/server/locksettings/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/logcat/TEST_MAPPING1
-rw-r--r--services/core/java/com/android/server/media/projection/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/notification/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING1
-rw-r--r--services/core/java/com/android/server/policy/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/power/TEST_MAPPING7
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java16
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java48
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/core/jni/TEST_MAPPING1
-rw-r--r--services/devicepolicy/TEST_MAPPING2
-rw-r--r--services/tests/InputMethodSystemServerTests/TEST_MAPPING2
-rw-r--r--services/tests/dreamservicetests/TEST_MAPPING1
-rw-r--r--services/tests/powerstatstests/TEST_MAPPING1
-rw-r--r--services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING2
-rw-r--r--services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java15
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java28
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java80
-rw-r--r--telephony/java/android/telephony/satellite/stub/ISatellite.aidl10
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java19
-rw-r--r--tests/utils/testutils/TEST_MAPPING3
-rw-r--r--tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java40
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt8
230 files changed, 4603 insertions, 1722 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08a09e1b1a73..b1f587e45a6d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -334,7 +334,7 @@ java_aconfig_library {
aconfig_declarations {
name: "android.app.flags-aconfig",
package: "android.app",
- srcs: ["core/java/android/app/activity_manager.aconfig"],
+ srcs: ["core/java/android/app/*.aconfig"],
}
java_aconfig_library {
diff --git a/Android.bp b/Android.bp
index f1a3af27a633..4c44974a84d6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -525,6 +525,7 @@ java_library {
required: [
"framework-minus-apex",
"framework-platform-compat-config",
+ "framework-location-compat-config",
"services-platform-compat-config",
"icu4j-platform-compat-config",
"TeleService-platform-compat-config",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 122e627b7067..b215ee4e2537 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -68,7 +68,6 @@
"name": "FrameworksInputMethodSystemServerTests",
"options": [
{"include-filter": "com.android.server.inputmethod"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
index b8fef63fa008..6924cb210adb 100644
--- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -7,7 +7,6 @@
],
"options": [
{"include-filter": "com.android.server.DeviceIdleControllerTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -29,4 +28,4 @@
]
}
]
-} \ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
index b76c582cf287..c06861164d31 100644
--- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.DeviceIdleControllerTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
@@ -17,4 +16,4 @@
]
}
]
-} \ No newline at end of file
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index 8504b1f0bdb1..e649485ed5e5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -3,8 +3,6 @@
{
"name": "CtsJobSchedulerTestCases",
"options": [
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.LargeTest"}
]
@@ -13,18 +11,16 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.job"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.job"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
}
],
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
index 73b00b6cfa24..e194b8dbe33d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.tare"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -12,7 +11,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.tare"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index 9ec799f73b41..a75415ec6151 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "CtsUsageStatsTestCases",
"options": [
{"include-filter": "android.app.usage.cts.UsageStatsTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.MediumTest"},
{"exclude-annotation": "androidx.test.filters.LargeTest"}
@@ -21,7 +20,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.usage"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
@@ -37,4 +35,4 @@
]
}
]
-} \ No newline at end of file
+}
diff --git a/boot/Android.bp b/boot/Android.bp
index 93d425e439a9..b33fab6e1a9f 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -208,6 +208,13 @@ custom_platform_bootclasspath {
],
}
+genrule { // This module exists to make the srcjar output available to Make.
+ name: "platform-bootclasspath.srcjar",
+ srcs: [":platform-bootclasspath{.srcjar}"],
+ out: ["platform-bootclasspath.srcjar"],
+ cmd: "cp $(in) $(out)",
+}
+
platform_systemserverclasspath {
name: "platform-systemserverclasspath",
}
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 82df55509a7a..e7361fe95e8e 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -48,6 +48,7 @@ Register a new uinput device
| `vid` | 16-bit integer | Vendor ID |
| `pid` | 16-bit integer | Product ID |
| `bus` | string | Bus that device should use |
+| `port` | string | `phys` value to report |
| `configuration` | object array | uinput device configuration|
| `ff_effects_max` | integer | `ff_effects_max` value |
| `abs_info` | array | Absolute axes information |
diff --git a/core/api/current.txt b/core/api/current.txt
index c9f06397abbf..f340e16383e4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5952,6 +5952,7 @@ package android.app {
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
@@ -42903,7 +42904,7 @@ package android.telephony {
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
- field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -43071,6 +43072,7 @@ package android.telephony {
field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -54454,8 +54456,8 @@ package android.view.animation {
public class AnimationUtils {
ctor public AnimationUtils();
method public static long currentAnimationTimeMillis();
- method public static long getExpectedPresentationTimeMillis();
- method public static long getExpectedPresentationTimeNanos();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d88e002b4bd..8748e6939ccd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3223,14 +3223,14 @@ package android.companion.virtual {
public final class VirtualDeviceParams implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
- method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
method public int getAudioPlaybackSessionId();
method public int getAudioRecordingSessionId();
- method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
- method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
- method public int getDefaultActivityPolicy();
- method public int getDefaultNavigationPolicy();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
+ method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
+ method @Deprecated public int getDefaultActivityPolicy();
+ method @Deprecated public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
method public int getLockState();
@@ -3238,15 +3238,15 @@ package android.companion.virtual {
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
- field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+ field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
- field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
- field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
+ field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
@@ -3257,12 +3257,12 @@ package android.companion.virtual {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
+ method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
@@ -16731,17 +16731,22 @@ package android.telephony.satellite {
}
public final class SatelliteManager {
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -16768,6 +16773,7 @@ package android.telephony.satellite {
field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 1905b6a46d7e..bc6fe6146764 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,12 +16,15 @@
package android.app;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -31,11 +34,15 @@ import java.util.Set;
*/
@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
public class GrammaticalInflectionManager {
- private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+
+ /** @hide */
+ @NonNull
+ public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
Configuration.GRAMMATICAL_GENDER_NEUTRAL,
Configuration.GRAMMATICAL_GENDER_FEMININE,
- Configuration.GRAMMATICAL_GENDER_MASCULINE));
+ Configuration.GRAMMATICAL_GENDER_MASCULINE)));
private final Context mContext;
private final IGrammaticalInflectionManager mService;
@@ -79,7 +86,7 @@ public class GrammaticalInflectionManager {
*/
public void setRequestedApplicationGrammaticalGender(
@Configuration.GrammaticalGender int grammaticalGender) {
- if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
throw new IllegalArgumentException("Unknown grammatical gender");
}
@@ -90,4 +97,44 @@ public class GrammaticalInflectionManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the current grammatical gender for all privileged applications. The value will be
+ * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)
+ *
+ * @param grammaticalGender the terms of address the user preferred in system.
+ *
+ * @see Configuration#getGrammaticalGender
+ * @hide
+ */
+ public void setSystemWideGrammaticalGender(
+ @Configuration.GrammaticalGender int grammaticalGender) {
+ if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ try {
+ mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current grammatical gender of privileged application from the encrypted file,
+ * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}.
+ *
+ * @return the value of grammatical gender
+ *
+ * @see Configuration#getGrammaticalGender
+ */
+ @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
+ @Configuration.GrammaticalGender
+ public int getSystemGrammaticalGender() {
+ try {
+ return mService.getSystemGrammaticalGender(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 9366a45551da..48a48416d592 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -16,4 +16,14 @@ package android.app;
* Sets a specified app’s app-specific grammatical gender.
*/
void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
- } \ No newline at end of file
+
+ /**
+ * Sets the grammatical gender to system.
+ */
+ void setSystemWideGrammaticalGender(int userId, int gender);
+
+ /**
+ * Gets the grammatical gender from system.
+ */
+ int getSystemGrammaticalGender(int userId);
+ }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d98e678..9cf54e3b9063 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS
per-file Broadcast* = file:/BROADCASTS_OWNERS
per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
+# GrammaticalInflectionManager
+per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+
# KeyguardManager
per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 315a0556042a..a29c196d88de 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -19,7 +19,7 @@
"name": "CtsAppOpsTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
new file mode 100644
index 000000000000..989ce61337a3
--- /dev/null
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+ name: "system_terms_of_address_enabled"
+ namespace: "grammatical_gender"
+ description: "Feature flag for System Terms of Address"
+ bug: "297798866"
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0fa78c88c863..0975cbb8684f 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -94,13 +94,19 @@ public final class VirtualDeviceParams implements Parcelable {
/**
* Indicates that activities are allowed by default on this virtual device, unless they are
* explicitly blocked by {@link Builder#setBlockedActivities}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
*/
+ @Deprecated
public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0;
/**
* Indicates that activities are blocked by default on this virtual device, unless they are
* allowed by {@link Builder#setAllowedActivities}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
*/
+ @Deprecated
public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1;
/** @hide */
@@ -113,13 +119,19 @@ public final class VirtualDeviceParams implements Parcelable {
/**
* Indicates that tasks are allowed to navigate to other tasks on this virtual device,
* unless they are explicitly blocked by {@link Builder#setBlockedCrossTaskNavigations}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT}
*/
+ @Deprecated
public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0;
/**
* Indicates that tasks are blocked from navigating to other tasks by default on this virtual
* device, unless allowed by {@link Builder#setAllowedCrossTaskNavigations}.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM}
*/
+ @Deprecated
public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
/** @hide */
@@ -325,7 +337,10 @@ public final class VirtualDeviceParams implements Parcelable {
* be be allowed by default.
*
* @see Builder#setAllowedCrossTaskNavigations(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getAllowedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
@@ -340,7 +355,10 @@ public final class VirtualDeviceParams implements Parcelable {
* will be be allowed by default.
*
* @see Builder#setBlockedCrossTaskNavigations(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getBlockedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
@@ -355,7 +373,10 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @see Builder#setAllowedCrossTaskNavigations
* @see Builder#setBlockedCrossTaskNavigations
+ *
+ * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
*/
+ @Deprecated
@NavigationPolicy
public int getDefaultNavigationPolicy() {
return mDefaultNavigationPolicy;
@@ -366,7 +387,10 @@ public final class VirtualDeviceParams implements Parcelable {
* allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getAllowedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
@@ -379,7 +403,10 @@ public final class VirtualDeviceParams implements Parcelable {
* that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
+ *
+ * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Set<ComponentName> getBlockedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
@@ -394,7 +421,10 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @see Builder#setBlockedActivities
* @see Builder#setAllowedActivities
+ *
+ * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY}
*/
+ @Deprecated
@ActivityPolicy
public int getDefaultActivityPolicy() {
return mDefaultActivityPolicy;
@@ -743,7 +773,11 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @param allowedCrossTaskNavigations A set of tasks {@link ComponentName} allowed to
* navigate to new tasks in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setAllowedCrossTaskNavigations(
@NonNull Set<ComponentName> allowedCrossTaskNavigations) {
@@ -774,7 +808,11 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @param blockedCrossTaskNavigations A set of tasks {@link ComponentName} to be
* blocked from navigating to new tasks in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setBlockedCrossTaskNavigations(
@NonNull Set<ComponentName> blockedCrossTaskNavigations) {
@@ -802,7 +840,11 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
* in the virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
if (mDefaultActivityPolicyConfigured
@@ -828,7 +870,11 @@ public final class VirtualDeviceParams implements Parcelable {
*
* @param blockedActivities A set of {@link ComponentName} to be blocked launching from
* virtual device.
+ *
+ * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and
+ * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption}
*/
+ @Deprecated
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
if (mDefaultActivityPolicyConfigured
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ab669cc141f1..b0ab11f48858 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -45,7 +45,8 @@
]
},
{
- "name":"CarrierAppIntegrationTestCases"
+ "name":"CarrierAppIntegrationTestCases",
+ "keywords": ["internal"]
},
{
"name":"CtsSilentUpdateHostTestCases"
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0c8eb02e9681..08d32c3b5cbc 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -13,3 +13,10 @@ flag {
description: "Feature flag to enable the archiving feature."
bug: "278553670"
}
+
+flag {
+ name: "stay_stopped"
+ namespace: "backstage_power"
+ description: "Feature flag to improve stopped state enforcement"
+ bug: "296644915"
+}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index ad3abd9b531c..2d6e09a1b268 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -8,7 +8,6 @@
"name": "FrameworksVibratorCoreTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -21,7 +20,6 @@
"name": "FrameworksVibratorServicesTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -34,7 +32,6 @@
"name": "CtsVibratorTestCases",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 80b7d40c14c5..4c8ef97a7437 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2940,6 +2940,12 @@ public class UserManager {
* Used to check if the context user is a restricted profile. Restricted profiles
* may have a reduced number of available apps, app restrictions, and account restrictions.
*
+ * <p>The caller must be in the same profile group as the context user or else hold
+ * <li>{@link android.Manifest.permission#MANAGE_USERS},
+ * <li>or {@link android.Manifest.permission#CREATE_USERS},
+ * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link android.Manifest.permission#QUERY_USERS}.
+ *
* @return whether the context user is a restricted profile.
* @hide
*/
@@ -2963,6 +2969,12 @@ public class UserManager {
* Check if a user is a restricted profile. Restricted profiles may have a reduced number of
* available apps, app restrictions, and account restrictions.
*
+ * <p>Requires
+ * <li>{@link android.Manifest.permission#MANAGE_USERS},
+ * <li>or {@link android.Manifest.permission#CREATE_USERS},
+ * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link android.Manifest.permission#QUERY_USERS}.
+ *
* @param user the user to check
* @return whether the user is a restricted profile.
* @hide
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index 7b8d52f51cee..468c4518602e 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsNotificationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -21,9 +18,6 @@
"name": "FrameworksUiServicesTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
index 0fe974afd15c..c9bd2cacb138 100644
--- a/core/java/android/text/TEST_MAPPING
+++ b/core/java/android/text/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsTextTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
new file mode 100644
index 000000000000..b0aa72a765cc
--- /dev/null
+++ b/core/java/android/text/flags/fix_double_underline.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "fix_double_underline"
+ namespace: "text"
+ description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+ bug: "297336724"
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5eaded218a0b..552263a16937 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1328,6 +1328,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto";
/**
+ * Hint indicating that the developer intends to fill this view with output from
+ * CredentialManager.
+ *
+ * @hide
+ */
+ public static final String AUTOFILL_HINT_CREDENTIAL_MANAGER = "credential";
+
+ /**
* Hints for the autofill services that describes the content of the view.
*/
private @Nullable String[] mAutofillHints;
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 8ba8b8cca5ed..a07b62fe2890 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,7 +16,11 @@
package android.view.animation;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
+import static android.view.flags.Flags.expectedPresentationTimeApi;
+
import android.annotation.AnimRes;
+import android.annotation.FlaggedApi;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
@@ -151,7 +155,12 @@ public class AnimationUtils {
* @return the expected presentation time of a frame in the
* {@link System#nanoTime()} time base.
*/
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
public static long getExpectedPresentationTimeNanos() {
+ if (!expectedPresentationTimeApi()) {
+ return SystemClock.uptimeMillis();
+ }
+
AnimationState state = sAnimationState.get();
return state.mExpectedPresentationTimeNanos;
}
@@ -164,6 +173,7 @@ public class AnimationUtils {
* @return the expected presentation time of a frame in the
* {@link SystemClock#uptimeMillis()} time base.
*/
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
public static long getExpectedPresentationTimeMillis() {
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 4cb8788ab9f2..6cf185a5b460 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1464,7 +1464,7 @@ public final class AutofillManager {
if (infos.size() == 0) {
throw new IllegalArgumentException("No VirtualViewInfo found");
}
- if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) {
+ if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ view.getAutofillId().toString());
@@ -1488,7 +1488,7 @@ public final class AutofillManager {
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
- if (v.isCredential()
+ if (isCredmanRequested(v)
&& mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
@@ -3434,6 +3434,22 @@ public final class AutofillManager {
}
}
+ private boolean isCredmanRequested(View view) {
+ if (view.isCredential()) {
+ return true;
+ }
+ String[] hints = view.getAutofillHints();
+ if (hints == null) {
+ return false;
+ }
+ for (String hint : hints) {
+ if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Find a single view by its id.
*
diff --git a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
index e3972ade8aa3..13a6f8d52dc6 100644
--- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "toolkit"
description: "Feature flag for toolkit to set frame rate"
bug: "293512962"
+}
+
+flag {
+ name: "expected_presentation_time_api"
+ namespace: "toolkit"
+ description: "Feature flag for using expected presentation time of the Choreographer"
+ bug: "278730197"
} \ No newline at end of file
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index d25c8a834c7b..7b7e34172fed 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -25,9 +25,12 @@ import android.window.WindowContainerTransaction;
interface ITaskFragmentOrganizerController {
/**
- * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system
+ * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional
+ * system capabilities.
*/
- void registerOrganizer(in ITaskFragmentOrganizer organizer);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)")
+ void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer);
/**
* Unregisters a previously registered TaskFragmentOrganizer.
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index f785a3d1514e..a6c9cecb508f 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -22,9 +22,11 @@ import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.os.Bundle;
import android.os.IBinder;
@@ -32,6 +34,8 @@ import android.os.RemoteException;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -140,12 +144,34 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
}
/**
- * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
*/
@CallSuper
public void registerOrganizer() {
+ // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed.
+ try {
+ getController().registerOrganizer(mInterface, false /* isSystemOrganizer */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
+ *
+ * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer
+ * will have additional system capabilities, including: (1) it will receive SurfaceControl for
+ * the organized TaskFragment, and (2) it needs to update the
+ * {@link android.view.SurfaceControl} following the window change accordingly.
+ *
+ * @hide
+ */
+ @CallSuper
+ @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true)
+ @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+ public void registerOrganizer(boolean isSystemOrganizer) {
try {
- getController().registerOrganizer(mInterface);
+ getController().registerOrganizer(mInterface, isSystemOrganizer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 3c5d60dc8ae2..4dada108c4c6 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -192,6 +193,9 @@ public final class TaskFragmentTransaction implements Parcelable {
@Nullable
private TaskFragmentParentInfo mTaskFragmentParentInfo;
+ @Nullable
+ private SurfaceControl mSurfaceControl;
+
public Change(@ChangeType int type) {
mType = type;
}
@@ -206,6 +210,7 @@ public final class TaskFragmentTransaction implements Parcelable {
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mActivityToken = in.readStrongBinder();
mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
+ mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR);
}
@Override
@@ -219,6 +224,7 @@ public final class TaskFragmentTransaction implements Parcelable {
dest.writeTypedObject(mActivityIntent, flags);
dest.writeStrongBinder(mActivityToken);
dest.writeTypedObject(mTaskFragmentParentInfo, flags);
+ dest.writeTypedObject(mSurfaceControl, flags);
}
/** The change is related to the TaskFragment created with this unique token. */
@@ -306,6 +312,13 @@ public final class TaskFragmentTransaction implements Parcelable {
return this;
}
+ /** @hide */
+ @NonNull
+ public Change setTaskFragmentSurfaceControl(@Nullable SurfaceControl sc) {
+ mSurfaceControl = sc;
+ return this;
+ }
+
@ChangeType
public int getType() {
return mType;
@@ -359,6 +372,21 @@ public final class TaskFragmentTransaction implements Parcelable {
return mTaskFragmentParentInfo;
}
+ /**
+ * Gets the {@link SurfaceControl} of the TaskFragment. This field is {@code null} for
+ * a regular {@link TaskFragmentOrganizer} and is only available for a system
+ * {@link TaskFragmentOrganizer} in the
+ * {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_APPEARED} event. See
+ * {@link ITaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer,
+ * boolean)}
+ *
+ * @hide
+ */
+ @Nullable
+ public SurfaceControl getTaskFragmentSurfaceControl() {
+ return mSurfaceControl;
+ }
+
@Override
public String toString() {
return "Change{ type=" + mType + " }";
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index ddfd0ed18bef..c09181f2f496 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsRoleTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
@@ -15,7 +15,7 @@
"include-filter": "android.permission.cts.PermissionControllerTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7eeac29e21f7..4d8eeac6d5ab 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -316,7 +316,14 @@ oneway interface IStatusBar
*/
void requestTileServiceListeningState(in ComponentName componentName);
- void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
+ void requestAddTile(
+ int callingUid,
+ in ComponentName componentName,
+ in CharSequence appName,
+ in CharSequence label,
+ in Icon icon,
+ in IAddTileResultCallback callback
+ );
void cancelRequestAddTile(in String packageName);
/** Notifies System UI about an update to the media tap-to-transfer sender state. */
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 878e6b306571..71d696ea4554 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -172,28 +172,17 @@
<integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer>
<java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" />
- <!-- Telephony config for services supported by satellite providers. The format of each config
- string in the array is as follows: "PLMN_1:service_1,service_2,..."
- where PLMN is the satellite PLMN of a provider and service is an integer with the
- following value:
- 1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
- 2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
- 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
- 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
- 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
- 6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}
- Example of a config string: "10011:2,3"
-
- The PLMNs not configured in this array will be ignored and will not be used for satellite
- scanning. -->
- <string-array name="config_satellite_services_supported_by_providers" translatable="false">
- </string-array>
- <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
+ <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
+ to identify providers that should be ignored if the carrier config
+ carrier_supported_satellite_services_per_provider_bundle does not support them.
+ -->
+ <string-array name="config_satellite_providers" translatable="false"></string-array>
+ <java-symbol type="array" name="config_satellite_providers" />
- <!-- The identifier of the satellite's eSIM profile preloaded on the device. The identifier is
- composed of MCC and MNC of the satellite PLMN with the format "mccmnc". -->
- <string name="config_satellite_esim_identifier" translatable="false"></string>
- <java-symbol type="string" name="config_satellite_esim_identifier" />
+ <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
+ of the satellite PLMN with the format "mccmnc". -->
+ <string name="config_satellite_sim_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_identifier" />
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 5d922961aa8b..49ed3a83e3d4 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,13 +18,20 @@ package android.graphics.drawable;
import static com.google.common.truth.Truth.assertThat;
+import android.app.IUriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -34,6 +41,7 @@ import com.android.frameworks.coretests.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -457,6 +465,81 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
+ @SmallTest
+ public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ false);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+ assertThat(loadedDrawable).isNotNull();
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight());
+ loadedDrawable.draw(new Canvas(test1));
+
+ bit1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-original.png")));
+ test1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-test.png")));
+ if (!equalBitmaps(bit1, test1)) {
+ findBitmapDifferences(bit1, test1);
+ fail("bitmap1 differs, check " + dir);
+ }
+ }
+
+ @SmallTest
+ public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ true);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ assertThat(loadedDrawable).isNull();
+ }
+
+
// ======== utils ========
static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
@@ -541,4 +624,77 @@ public class IconTest extends AndroidTestCase {
}
L(sb.toString());
}
-}
+
+ private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {
+
+ final ArrayList<CheckRequest> mRequests = new ArrayList<>();
+ final boolean mRejectCheckRequests;
+
+ TestableIUriGrantsManager(boolean rejectCheckRequests) {
+ this.mRejectCheckRequests = rejectCheckRequests;
+ }
+
+ @Override
+ public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
+ int i2, int i3) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void clearGrantedUriPermissions(String s, int i) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int checkGrantUriPermission_ignoreNonSystem(
+ int uid, String packageName, Uri uri, int mode, int userId)
+ throws RemoteException {
+ CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
+ mRequests.add(r);
+ if (mRejectCheckRequests) {
+ throw new SecurityException();
+ } else {
+ return uid;
+ }
+ }
+
+ static class CheckRequest {
+ final int mCallingUid;
+ final String mPackageName;
+ final Uri mUri;
+ final int mMode;
+ final int mUserId;
+
+ CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
+ this.mCallingUid = callingUid;
+ this.mPackageName = packageName;
+ this.mUri = uri;
+ this.mMode = mode;
+ this.mUserId = userId;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a8b40325a713..a5bbeb58bc08 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -17,6 +17,7 @@
package android.window.flags;
import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
import android.platform.test.annotations.Presubmit;
@@ -42,4 +43,10 @@ public class WindowFlagsTest {
// No crash when accessing the flag.
syncWindowConfigUpdateFlag();
}
+
+ @Test
+ public void testTaskFragmentSystemOrganizerFlag() {
+ // No crash when accessing the flag.
+ taskFragmentSystemOrganizerFlag();
+ }
}
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
index 2f3afa6f6399..54a5ff1d675d 100644
--- a/core/tests/vibrator/TEST_MAPPING
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksVibratorCoreTests",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 708feeb9e421..5509f000aca5 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -24,9 +24,12 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.IUriGrantsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@ import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresPermission;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -530,6 +536,46 @@ public final class Icon implements Parcelable {
return loadDrawable(context);
}
+ /**
+ * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
+ * to load the resource. The check will be performed using the permissions of the passed uid,
+ * and not those of the caller.
+ * <p>
+ * This should be called for {@link Icon} objects that come from a not trusted source and may
+ * contain a URI.
+ *
+ * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
+ * return {@code null}.
+ *
+ * @see #loadDrawable
+ *
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public Drawable loadDrawableCheckingUriGrant(
+ Context context,
+ IUriGrantsManager iugm,
+ int callingUid,
+ String packageName
+ ) {
+ if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
+ try {
+ iugm.checkGrantUriPermission_ignoreNonSystem(
+ callingUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(getUri()),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(getUri())
+ );
+ } catch (SecurityException | RemoteException e) {
+ Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
+ return null;
+ }
+ }
+ return loadDrawable(context);
+ }
+
/** @hide */
public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7faf3803b11a..705e38731a0c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -435,10 +435,10 @@
<dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in desktop mode, but not freeform. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">104dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in freeform desktop windowing mode. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">104dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">52dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3d825f072a6a..4ea14f473c39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -720,6 +720,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -767,6 +769,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
options1 = options1 != null ? options1 : new Bundle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index f82b212c344c..a7a11dee80f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -163,6 +163,9 @@ class HandleMenu {
final ImageButton splitscreenBtn = windowingPillView.findViewById(
R.id.split_screen_button);
final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
+ // TODO: Remove once implemented.
+ floatingBtn.setVisibility(View.GONE);
+
final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
fullscreenBtn.setOnClickListener(mOnClickListener);
splitscreenBtn.setOnClickListener(mOnClickListener);
@@ -196,6 +199,9 @@ class HandleMenu {
}
final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
selectBtn.setOnClickListener(mOnClickListener);
+ final Button screenshotBtn = moreActionsPillView.findViewById(R.id.screenshot_button);
+ // TODO: Remove once implemented.
+ screenshotBtn.setVisibility(View.GONE);
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index b0e847f69cd5..e37d806c7a14 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
import org.junit.Test
open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index f847e400bebd..2a50912e0a5c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Rule
import org.junit.Test
open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 31e52e2f2ae8..d5da1a8b558c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 0870073c5050..7fdcb9be62ee 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 1bb6c4596cd7..308e954b86c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index bb084d6834b0..39e75bd25a71 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Rule
import org.junit.Test
open class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index bc5857f06120..e18da17175c0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
import org.junit.Test
open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index e4faa4a9116f..00d60e756ffa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Rule
import org.junit.Test
open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 7431974951a7..d7efbc8c0fd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index c2b060975604..4eece3f62d10 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 7d1072dc19c2..d96b056d8753 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 79760d1f23d7..809b690e0861 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index ff3729ab96c0..bbdf2d728494 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index ffbcfe43f374..5c29fd8fe57e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 5e937c093eb7..a7398ebf56e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index 89db8a0c7a76..eae88ad4ad09 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index bf4ee7a9bb84..7e8ee04a28fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 7e96f066c1c1..9295c330b879 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Rule
import org.junit.Test
open class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index a84deac7698b..4b59e9fbd866 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
import org.junit.Test
open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index afcc743e650d..5ff36d4aabbb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Rule
import org.junit.Test
open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 2f96f5f99a66..c0cb7219437b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index efbeb769508f..8c140884aa50 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 232fccc12b1c..7b6614b81c11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index d7f2845553f3..5df5be9daa8b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 019c946068dd..9d63003bf2a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 567886103c61..9fa04b208ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Rule
import org.junit.Test
open class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index b3c4ea2c7c65..9386aa2b2cf0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
import org.junit.Test
open class SwitchBetweenSplitPairsGesturalNavLandscape :
SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index f88180f40196..5ef21672bfe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Rule
import org.junit.Test
open class SwitchBetweenSplitPairsGesturalNavPortrait :
SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 391cb9b7c90c..9caab9b5182a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -18,18 +18,13 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index 4af133ddaa21..bf484e5cef98 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -18,18 +18,13 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.rules.FlickerServiceRule
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
- @get:Rule
- val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFlicker = false)
-
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e1dd145edcfa..ff1eedb8eacb 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -143,6 +143,7 @@ cc_defaults {
"libcrypto",
"libsync",
"libui",
+ "aconfig_text_flags_c_lib",
],
static_libs: [
"libEGL_blobCache",
@@ -712,11 +713,13 @@ cc_test {
],
static_libs: [
+ "libflagtest",
"libgmock",
"libhwui_static",
],
shared_libs: [
"libmemunreachable",
+ "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
@@ -756,6 +759,7 @@ cc_test {
"tests/unit/TestUtilsTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
+ "tests/unit/UnderlineTest.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 000000000000..ffb329d9f8e6
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+ return com_android_text_flags_fix_double_underline();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+} // namespace text_feature
+
+} // namespace android
+
+#endif // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8394c3cd4175..31fc929dfcdf 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
#include <utility>
#include "CanvasProperty.h"
+#include "FeatureFlags.h"
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
@@ -795,7 +796,9 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai
sk_sp<SkTextBlob> textBlob(builder.make());
applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
- drawTextDecorations(x, y, totalAdvance, paintCopy);
+ if (!text_feature::fix_double_underline()) {
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
+ }
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2351797ac787..80b6c0385fca 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
#include "Canvas.h"
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "RenderNode.h"
#include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
#include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
namespace android {
@@ -34,13 +35,6 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
- const Paint& paint, Canvas* canvas) {
- const SkScalar strokeWidth = fmax(thickness, 1.0f);
- const SkScalar bottom = top + strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
-}
-
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
// paint has already been filtered by our caller, so we can ignore any filter
const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa
}
}
-static void simplifyPaint(int color, Paint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
- : layout(layout)
- , canvas(canvas)
- , paint(paint)
- , x(x)
- , y(y)
- , totalAdvance(totalAdvance) {}
-
- void operator()(size_t start, size_t end) {
- auto glyphFunc = [&](uint16_t* text, float* positions) {
- for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
- text[textIndex++] = layout.getGlyphId(i);
- positions[posIndex++] = x + layout.getX(i);
- positions[posIndex++] = y + layout.getY(i);
- }
- };
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
- Paint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
- // inner
- gDrawTextBlobMode = DrawTextBlobMode::HctInner;
- Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
- gDrawTextBlobMode = DrawTextBlobMode::Normal;
- } else {
- // standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
- }
- }
-
-private:
- const minikin::Layout& layout;
- Canvas* canvas;
- const Paint& paint;
- float x;
- float y;
- float totalAdvance;
-};
-
void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
int glyphCount, const Paint& paint) {
// Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -182,6 +109,31 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count,
DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
+
+ if (text_feature::fix_double_underline()) {
+ Paint copied(paint);
+ PaintFilter* filter = getPaintFilter();
+ if (filter != nullptr) {
+ filter->filterFullPaint(&copied);
+ }
+ const bool isUnderline = copied.isUnderline();
+ const bool isStrikeThru = copied.isStrikeThru();
+ if (isUnderline || isStrikeThru) {
+ const SkScalar left = x;
+ const SkScalar right = x + layout.getAdvance();
+ if (isUnderline) {
+ const SkScalar top = y + f.getUnderlinePosition();
+ drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+ }
+ if (isStrikeThru) {
+ float textSize = paint.getSkFont().getSize();
+ const float position = textSize * Paint::kStdStrikeThru_Top;
+ const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+ const SkScalar top = y + position;
+ drawStroke(left, right, top, thickness, copied, this);
+ }
+ }
+ }
}
void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d34d23..9ec023b2c36f 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@ protected:
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y,float totalAdvance) = 0;
+ float y, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 000000000000..2e6e97634aec
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+ const Paint& paint, Canvas* canvas) {
+ const SkScalar strokeWidth = fmax(thickness, 1.0f);
+ const SkScalar bottom = top + strokeWidth;
+ canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+ float y, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , totalAdvance(totalAdvance)
+ , underlinePosition(0)
+ , underlineThickness(0) {}
+
+ void operator()(size_t start, size_t end) {
+ auto glyphFunc = [&](uint16_t* text, float* positions) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ };
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+ Paint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+ // inner
+ gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+ Paint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+ gDrawTextBlobMode = DrawTextBlobMode::Normal;
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+ }
+
+ if (text_feature::fix_double_underline()) {
+ // Extract underline position and thickness.
+ if (paint.isUnderline()) {
+ SkFontMetrics metrics;
+ paint.getSkFont().getMetrics(&metrics);
+ const float textSize = paint.getSkFont().getSize();
+ SkScalar position;
+ if (!metrics.hasUnderlinePosition(&position)) {
+ position = textSize * Paint::kStdUnderline_Top;
+ }
+ SkScalar thickness;
+ if (!metrics.hasUnderlineThickness(&thickness)) {
+ thickness = textSize * Paint::kStdUnderline_Thickness;
+ }
+
+ // If multiple fonts are used, use the most bottom position and most thick stroke
+ // width as the underline position. This follows the CSS standard:
+ // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+ // <quote>
+ // The exact position and thickness of line decorations is UA-defined in this level.
+ // However, for underlines and overlines the UA must use a single thickness and
+ // position on each line for the decorations deriving from a single decorating box.
+ // </quote>
+ underlinePosition = std::max(underlinePosition, position);
+ underlineThickness = std::max(underlineThickness, thickness);
+ }
+ }
+ }
+
+ float getUnderlinePosition() const { return underlinePosition; }
+ float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+ const minikin::Layout& layout;
+ Canvas* canvas;
+ const Paint& paint;
+ float x;
+ float y;
+ float totalAdvance;
+ float underlinePosition;
+ float underlineThickness;
+};
+
+} // namespace android
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7ee247..0fffee744be0 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@ static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobj
p.writeFloat(info.fDisplayRatioHdr);
// base image type
p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
- // type
- p.writeInt32(static_cast<int32_t>(info.fType));
#else
doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
@@ -232,7 +230,6 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job
info.fDisplayRatioSdr = p.readFloat();
info.fDisplayRatioHdr = p.readFloat();
info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
- info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
fromJava(nativeObject)->info = info;
#else
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 000000000000..db2be20936fb
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+ std::vector<minikin::FontVariation>());
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
+ fonts.push_back(minikin::Font::Builder(font).build());
+ return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+ return Typeface::createFromFamilies(
+ std::vector<std::shared_ptr<minikin::FontFamily>>(
+ {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+ return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+ 0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+ // Create canvas
+ SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+ sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ SkiaCanvas canvas(skBitmap);
+
+ // Create minikin::Layout
+ std::unique_ptr<Typeface> typeface(makeTypeface());
+ minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, paint, f);
+ return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // the text is "abc"
+ DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+ EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is あいう in Japanese
+ DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is aいc. The only middle of the character is Japanese.
+ DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+ // We use the bottom, thicker line as underline. Here, use Noto's one.
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+} // namespace
diff --git a/location/Android.bp b/location/Android.bp
index cfe0e494432c..eb7cd01111b2 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -39,3 +39,8 @@ java_sdk_library {
],
},
}
+
+platform_compat_config {
+ name: "framework-location-compat-config",
+ src: ":framework-location",
+}
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index a792498b8521..7aa9118e45ee 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "MediaProjectionTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index a4e5fb6a6165..196b5c3112c0 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -401,7 +401,9 @@ public class TvView extends ViewGroup {
private void resetInternal() {
mSessionCallback = null;
- mPendingAppPrivateCommands.clear();
+ synchronized (mPendingAppPrivateCommands) {
+ mPendingAppPrivateCommands.clear();
+ }
if (mSession != null) {
setSessionSurface(null);
removeSessionOverlayView();
@@ -691,7 +693,10 @@ public class TvView extends ViewGroup {
} else {
Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
+ "\" pending)");
- mPendingAppPrivateCommands.add(Pair.create(action, data));
+
+ synchronized (mPendingAppPrivateCommands) {
+ mPendingAppPrivateCommands.add(Pair.create(action, data));
+ }
}
}
@@ -1320,10 +1325,13 @@ public class TvView extends ViewGroup {
mSession = session;
if (session != null) {
// Sends the pending app private commands first.
- for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
- mSession.sendAppPrivateCommand(command.first, command.second);
+
+ synchronized (mPendingAppPrivateCommands) {
+ for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
+ mSession.sendAppPrivateCommand(command.first, command.second);
+ }
+ mPendingAppPrivateCommands.clear();
}
- mPendingAppPrivateCommands.clear();
synchronized (sMainTvViewLock) {
if (hasWindowFocus() && TvView.this == sMainTvView.get()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ea7735681f..49bd9d994088 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -345,6 +345,8 @@
<string name="accessibility_wifi_three_bars">Wifi three bars.</string>
<!-- Content description of the WIFI signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_wifi_signal_full">Wifi signal full.</string>
+ <!-- Content description of the WIFI signal when the WIFI is connected using the signal from a different device owned by the user. For accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
+ <string name="accessibility_wifi_other_device">Connected to your device.</string>
<!-- Content description of the Wi-Fi security type. This message indicates this is an open Wi-Fi (no password needed) [CHAR LIMIT=NONE] -->
<string name="accessibility_wifi_security_type_none">Open network</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index ee65ef4e92b6..ce466dfbf19c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -50,6 +50,7 @@ public class AccessibilityContentDescriptions {
};
public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+ public static final int WIFI_OTHER_DEVICE_CONNECTION = R.string.accessibility_wifi_other_device;
public static final int NO_CALLING = R.string.accessibility_no_calling;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 104f3d2b2621..ee05f2d9101b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,8 @@ filegroup {
"tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
"tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
"tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
+ "tests/src/com/android/systemui/qs/tiles/base/**/*.kt",
+ "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 5c2f979ac639..0623d4a4b7fb 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -69,7 +69,7 @@
"exclude-annotation": "org.junit.Ignore"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 510fa1e1fa80..42d088f218a1 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -34,7 +34,9 @@ android_library {
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
+ "androidx.compose.material3_material3-window-size-class",
"androidx.savedstate_savedstate",
+ "androidx.window_window",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
index b4e90d63c6b8..06618704e085 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
@@ -29,6 +29,8 @@ import com.android.compose.theme.typography.TypefaceNames
import com.android.compose.theme.typography.TypefaceTokens
import com.android.compose.theme.typography.TypographyTokens
import com.android.compose.theme.typography.platformTypography
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.compose.windowsizeclass.calculateWindowSizeClass
/** The Material 3 theme that should wrap all Platform Composables. */
@Composable
@@ -51,10 +53,12 @@ fun PlatformTheme(
remember(typefaceNames) {
platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
}
+ val windowSizeClass = calculateWindowSizeClass()
MaterialTheme(colorScheme, typography = typography) {
CompositionLocalProvider(
LocalAndroidColorScheme provides androidColorScheme,
+ LocalWindowSizeClass provides windowSizeClass,
) {
content()
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
new file mode 100644
index 000000000000..4674d6e5f25a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.compose.windowsizeclass
+
+import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
+import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.window.layout.WindowMetricsCalculator
+
+val LocalWindowSizeClass =
+ staticCompositionLocalOf<WindowSizeClass> {
+ throw IllegalStateException(
+ "No WindowSizeClass configured. Make sure to use LocalWindowSizeClass in a Composable" +
+ " surrounded by a PlatformTheme {}."
+ )
+ }
+
+@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
+@Composable
+fun calculateWindowSizeClass(): WindowSizeClass {
+ // Observe view configuration changes and recalculate the size class on each change.
+ LocalConfiguration.current
+ val density = LocalDensity.current
+ val context = LocalContext.current
+ val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
+ return WindowSizeClass.calculateFromSize(size)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d0f2ce84655d..a61e95931222 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -14,40 +14,49 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalMaterial3Api::class)
-
package com.android.systemui.bouncer.ui.composable
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -103,99 +112,234 @@ private fun SceneScope.BouncerScene(
dialogFactory: BouncerSceneDialogFactory,
modifier: Modifier = Modifier,
) {
- val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
- val authMethodViewModel: AuthMethodBouncerViewModel? by
- viewModel.authMethodViewModel.collectAsState()
- val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
- var dialog: Dialog? by remember { mutableStateOf(null) }
val backgroundColor = MaterialTheme.colorScheme.surface
+ val windowSizeClass = LocalWindowSizeClass.current
Box(modifier) {
Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
drawRect(color = backgroundColor)
}
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(60.dp),
- modifier =
- Modifier.element(Bouncer.Elements.Content)
- .fillMaxSize()
- .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
- ) {
- Crossfade(
- targetState = message,
- label = "Bouncer message",
- animationSpec = if (message.isUpdateAnimated) tween() else snap(),
- ) { message ->
- Text(
- text = message.text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.bodyLarge,
+ val childModifier = Modifier.element(Bouncer.Elements.Content).fillMaxSize()
+
+ when (windowSizeClass.widthSizeClass) {
+ WindowWidthSizeClass.Expanded ->
+ SideBySide(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ modifier = childModifier,
)
- }
+ WindowWidthSizeClass.Medium ->
+ Stacked(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ modifier = childModifier,
+ )
+ else ->
+ Bouncer(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ modifier = childModifier,
+ )
+ }
+ }
+}
- Box(Modifier.weight(1f)) {
- when (val nonNullViewModel = authMethodViewModel) {
- is PinBouncerViewModel ->
- PinBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PasswordBouncerViewModel ->
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PatternBouncerViewModel ->
- PatternBouncer(
- viewModel = nonNullViewModel,
- modifier =
- Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
- .align(Alignment.BottomCenter),
- )
- else -> Unit
- }
- }
+/**
+ * Renders the contents of the actual bouncer UI, the area that takes user input to do an
+ * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.).
+ */
+@Composable
+private fun Bouncer(
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerSceneDialogFactory,
+ modifier: Modifier = Modifier,
+) {
+ val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+ val authMethodViewModel: AuthMethodBouncerViewModel? by
+ viewModel.authMethodViewModel.collectAsState()
+ val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
+ var dialog: Dialog? by remember { mutableStateOf(null) }
- Button(
- onClick = viewModel::onEmergencyServicesButtonClicked,
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- ),
- ) {
- Text(
- text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
- style = MaterialTheme.typography.bodyMedium,
- )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(60.dp),
+ modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
+ ) {
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ ) { message ->
+ Text(
+ text = message.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+
+ Box(Modifier.weight(1f)) {
+ when (val nonNullViewModel = authMethodViewModel) {
+ is PinBouncerViewModel ->
+ PinBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PasswordBouncerViewModel ->
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PatternBouncerViewModel ->
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+ .align(Alignment.BottomCenter),
+ )
+ else -> Unit
}
+ }
+
+ Button(
+ onClick = viewModel::onEmergencyServicesButtonClicked,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
- if (dialogMessage != null) {
- if (dialog == null) {
- dialog =
- dialogFactory().apply {
- setMessage(dialogMessage)
- setButton(
- DialogInterface.BUTTON_NEUTRAL,
- context.getString(R.string.ok),
- ) { _, _ ->
- viewModel.onThrottlingDialogDismissed()
- }
- setCancelable(false)
- setCanceledOnTouchOutside(false)
- show()
+ if (dialogMessage != null) {
+ if (dialog == null) {
+ dialog =
+ dialogFactory().apply {
+ setMessage(dialogMessage)
+ setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.ok),
+ ) { _, _ ->
+ viewModel.onThrottlingDialogDismissed()
}
- }
- } else {
- dialog?.dismiss()
- dialog = null
+ setCancelable(false)
+ setCanceledOnTouchOutside(false)
+ show()
+ }
}
+ } else {
+ dialog?.dismiss()
+ dialog = null
}
}
}
+/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
+@Composable
+private fun UserSwitcher(
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier) {
+ Text(
+ text = "TODO: the user switcher goes here",
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ */
+@Composable
+private fun SideBySide(
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerSceneDialogFactory,
+ modifier: Modifier = Modifier,
+) {
+ val layoutDirection = LocalLayoutDirection.current
+ val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+ val (isUserSwitcherFirst, setUserSwitcherFirst) =
+ rememberSaveable(isLeftToRight) { mutableStateOf(isLeftToRight) }
+
+ Row(
+ modifier =
+ modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onDoubleTap = { offset ->
+ // Depending on where the user double tapped, switch the elements such that
+ // the bouncer contents element is closer to the side that was double
+ // tapped.
+ setUserSwitcherFirst(offset.x > size.width / 2)
+ }
+ )
+ },
+ ) {
+ val animatedOffset by
+ animateFloatAsState(
+ targetValue =
+ if (isUserSwitcherFirst) {
+ // When the user switcher is first, both elements have their natural
+ // placement so they are not offset in any way.
+ 0f
+ } else if (isLeftToRight) {
+ // Since the user switcher is not first, the elements have to be swapped
+ // horizontally. In the case of LTR locales, this means pushing the user
+ // switcher to the right, hence the positive number.
+ 1f
+ } else {
+ // Since the user switcher is not first, the elements have to be swapped
+ // horizontally. In the case of RTL locales, this means pushing the user
+ // switcher to the left, hence the negative number.
+ -1f
+ },
+ label = "offset",
+ )
+
+ UserSwitcher(
+ modifier =
+ Modifier.fillMaxHeight().weight(1f).graphicsLayer {
+ translationX = size.width * animatedOffset
+ },
+ )
+ Bouncer(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ modifier =
+ Modifier.fillMaxHeight().weight(1f).graphicsLayer {
+ // A negative sign is used to make sure this is offset in the direction that's
+ // opposite of the direction that the user switcher is pushed in.
+ translationX = -size.width * animatedOffset
+ },
+ )
+ }
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other. */
+@Composable
+private fun Stacked(
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerSceneDialogFactory,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ ) {
+ UserSwitcher(
+ modifier = Modifier.fillMaxWidth().weight(1f),
+ )
+ Bouncer(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ modifier = Modifier.fillMaxWidth().weight(1f),
+ )
+ }
+}
+
interface BouncerSceneDialogFactory {
operator fun invoke(): AlertDialog
}
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 569dd4cf9252..a51c55ee965f 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -52,7 +52,7 @@
style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/cancel" />
+ android:text="@string/dismiss_dialog" />
<Space
android:layout_width="0dp"
@@ -64,6 +64,6 @@
style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/enable_display" />
+ android:text="@string/mirror_display" />
</LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 85fb3ac577bc..587caaf3ecf3 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -44,6 +44,6 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index e63229aea70b..fc6d20e11d3b 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -38,6 +38,6 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b6bca65b8174..18f24ec6293e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -88,7 +88,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService)
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService)
</string>
<!-- The class path of the Safety Quick Settings Tile -->
@@ -604,7 +604,7 @@
<!-- Whether to force split shade.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
- TODO (b/293290851) - change this comment/resource when flag is enabled -->
+ TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">false</bool>
<!-- Whether we use large screen shade header which takes only one row compared to QS header -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5860806c6aec..a2637d5e55c3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,10 @@
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
-
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
- <string name="enable_display">Enable display</string>
+ <string name="mirror_display">Mirror display</string>
+ <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+ <string name="dismiss_dialog">Dismiss</string>
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone &amp; Camera</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index b2287d876a48..51dafac7b421 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -68,8 +69,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -77,6 +76,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
@@ -84,6 +84,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -420,7 +421,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
};
private final UserInteractor mUserInteractor;
- private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
+ private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
@@ -457,7 +458,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
- Provider<AuthenticationInteractor> authenticationInteractor
+ Provider<DeviceEntryInteractor> deviceEntryInteractor
) {
super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -487,7 +488,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mUserInteractor = userInteractor;
- mAuthenticationInteractor = authenticationInteractor;
+ mDeviceEntryInteractor = deviceEntryInteractor;
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mDeviceProvisionedController = deviceProvisionedController;
@@ -519,9 +520,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// When the scene framework says that the lockscreen has been dismissed, dismiss the
// keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mAuthenticationInteractor.get().isLockscreenDismissed(),
- isLockscreenDismissed -> {
- if (isLockscreenDismissed) {
+ mDeviceEntryInteractor.get().isDeviceEntered(),
+ isDeviceEntered -> {
+ if (isDeviceEntered) {
final int selectedUserId = mUserInteractor.getSelectedUserId();
showNextSecurityScreenOrFinish(
/* authenticated= */ true,
@@ -1081,15 +1082,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
* one side).
*/
private boolean canUseOneHandedBouncer() {
- switch(mCurrentSecurityMode) {
- case PIN:
- case Pattern:
- case SimPin:
- case SimPuk:
- return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
- default:
- return false;
- }
+ return switch (mCurrentSecurityMode) {
+ case PIN, Pattern, SimPin, SimPuk -> getResources().getBoolean(
+ R.bool.can_use_one_handed_bouncer);
+ default -> false;
+ };
}
private boolean canDisplayUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 165c4bb018d3..a81069a1f7db 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -424,11 +424,11 @@ public class LockIconViewController implements Dumpable {
private void updateConfiguration() {
WindowManager windowManager = mContext.getSystemService(WindowManager.class);
Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
- WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
mWidthPixels = bounds.right;
if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
// Assumed to be initially neglected as there are no left or right insets in portrait
// However, on landscape, these insets need to included when calculating the midpoint
+ WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight();
}
mHeightPixels = bounds.bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index b2433d4832f4..80be008dbd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -29,9 +29,9 @@ import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -59,18 +59,6 @@ import kotlinx.coroutines.withContext
/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
-
- /**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None); in such cases, the value of this
- * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
- * by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean>
-
/**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
@@ -129,14 +117,6 @@ interface AuthenticationRepository {
/** Returns the length of the PIN or `0` if the current auth method is not PIN. */
suspend fun getPinLength(): Int
- /**
- * Returns whether the lockscreen is enabled.
- *
- * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
- * is considered not secure (for example, "swipe" is considered to be "none").
- */
- suspend fun isLockscreenEnabled(): Boolean
-
/** Reports an authentication attempt. */
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
@@ -167,6 +147,7 @@ interface AuthenticationRepository {
suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
}
+@SysUISingleton
class AuthenticationRepositoryImpl
@Inject
constructor(
@@ -174,20 +155,10 @@ constructor(
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- keyguardRepository: KeyguardRepository,
private val lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
) : AuthenticationRepository {
- override val isUnlocked = keyguardRepository.isKeyguardUnlocked
-
- override suspend fun isLockscreenEnabled(): Boolean {
- return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUserId
- !lockPatternUtils.isLockScreenDisabled(selectedUserId)
- }
- }
-
override val isAutoConfirmEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = false,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 4cfc6aaa2b50..453a7a6d3536 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -26,9 +26,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationThrottling
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -42,15 +40,19 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-/** Hosts application business logic related to authentication. */
+/**
+ * Hosts application business logic related to user authentication.
+ *
+ * Note: there is a distinction between authentication (determining a user's identity) and device
+ * entry (dismissing the lockscreen). For logic that is specific to device entry, please use
+ * `DeviceEntryInteractor` instead.
+ */
@SysUISingleton
class AuthenticationInteractor
@Inject
@@ -59,8 +61,7 @@ constructor(
private val repository: AuthenticationRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
- private val keyguardRepository: KeyguardRepository,
- sceneInteractor: SceneInteractor,
+ private val deviceEntryRepository: DeviceEntryRepository,
private val clock: SystemClock,
) {
/**
@@ -77,76 +78,13 @@ constructor(
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
* the current authentication method is "swipe", the user does not need to complete any
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
- * get past it. This also means that the value of [isUnlocked] remains `false` even when the
- * lockscreen is showing and still needs to be dismissed by the user to proceed.
+ * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+ * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
- /**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
- * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
- * dismissed by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean> =
- combine(
- repository.isUnlocked,
- authenticationMethod,
- ) { isUnlocked, authenticationMethod ->
- !authenticationMethod.isSecure || isUnlocked
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
-
- /**
- * Whether the lockscreen has been dismissed (by any method). This can be false even when the
- * device is unlocked, e.g. when swipe to unlock is enabled.
- *
- * Note:
- * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
- * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
- * transition occurs).
- */
- val isLockscreenDismissed: StateFlow<Boolean> =
- sceneInteractor.desiredScene
- .map { it.key }
- .filter { currentScene ->
- currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
- }
- .map { it == SceneKey.Gone }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
- /**
- * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
- * authentication. This returns false whenever the lockscreen has been dismissed.
- *
- * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
- * UI.
- */
- val canSwipeToDismiss =
- combine(authenticationMethod, isLockscreenDismissed) {
- authenticationMethod,
- isLockscreenDismissed ->
- authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
- !isLockscreenDismissed
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
@@ -211,32 +149,15 @@ constructor(
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
* the current authentication method is "swipe", the user does not need to complete any
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
- * get past it. This also means that the value of [isUnlocked] remains `false` even when the
- * lockscreen is showing and still needs to be dismissed by the user to proceed.
+ * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains
+ * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
return repository.getAuthenticationMethod().toDomainLayer()
}
/**
- * Returns `true` if the device currently requires authentication before content can be viewed;
- * `false` if content can be displayed without unlocking first.
- */
- suspend fun isAuthenticationRequired(): Boolean {
- return !isUnlocked.value && getAuthenticationMethod().isSecure
- }
-
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- fun isBypassEnabled(): Boolean {
- return keyguardRepository.isBypassEnabled()
- }
-
- /**
* Attempts to authenticate the user and unlock the device.
*
* If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -312,7 +233,7 @@ constructor(
/** Starts refreshing the throttling state every second. */
private suspend fun startThrottlingCountdown() {
- cancelCountdown()
+ cancelThrottlingCountdown()
throttlingCountdownJob =
applicationScope.launch {
while (refreshThrottling() > 0) {
@@ -322,14 +243,14 @@ constructor(
}
/** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
- private fun cancelCountdown() {
+ private fun cancelThrottlingCountdown() {
throttlingCountdownJob?.cancel()
throttlingCountdownJob = null
}
/** Notifies that the currently-selected user has changed. */
private suspend fun onSelectedUserChanged() {
- cancelCountdown()
+ cancelThrottlingCountdown()
if (refreshThrottling() > 0) {
startThrottlingCountdown()
}
@@ -378,7 +299,7 @@ constructor(
DomainLayerAuthenticationMethodModel {
return when (this) {
is DataLayerAuthenticationMethodModel.None ->
- if (repository.isLockscreenEnabled()) {
+ if (deviceEntryRepository.isInsecureLockscreenEnabled()) {
DomainLayerAuthenticationMethodModel.Swipe
} else {
DomainLayerAuthenticationMethodModel.None
@@ -394,13 +315,10 @@ constructor(
/** Result of a user authentication attempt. */
enum class AuthenticationResult {
- /** Authentication succeeded and the device is now unlocked. */
+ /** Authentication succeeded. */
SUCCEEDED,
- /** Authentication failed and the device remains unlocked. */
+ /** Authentication failed. */
FAILED,
- /**
- * Authentication was not performed, e.g. due to insufficient input, and the device remains
- * unlocked.
- */
+ /** Authentication was not performed, e.g. due to insufficient input. */
SKIPPED,
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index f3a463ba44a4..0c0236999e39 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -50,6 +51,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
private val repository: BouncerRepository,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
flags: SceneContainerFlags,
@@ -144,7 +146,7 @@ constructor(
message: String? = null,
) {
applicationScope.launch {
- if (authenticationInteractor.isAuthenticationRequired()) {
+ if (deviceEntryInteractor.isAuthenticationRequired()) {
repository.setMessage(
message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 9b93522b2d5f..e8a84449566d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,26 +18,25 @@ package com.android.systemui.controls.ui
import android.app.Activity
import android.app.ActivityOptions
-import android.app.ActivityTaskManager
-import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.Dialog
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsets.Type
import android.view.WindowManager
import android.widget.ImageView
+import androidx.annotation.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.res.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.taskview.TaskView
/**
@@ -65,8 +64,8 @@ class DetailDialog(
private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL"
}
- var detailTaskId = INVALID_TASK_ID
private lateinit var taskViewContainer: View
+ private lateinit var controlDetailRoot: View
private val taskWidthPercentWidth = activityContext.resources.getFloat(
R.dimen.controls_task_view_width_percentage
)
@@ -79,12 +78,7 @@ class DetailDialog(
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
- fun removeDetailTask() {
- if (detailTaskId == INVALID_TASK_ID) return
- ActivityTaskManager.getInstance().removeTask(detailTaskId)
- detailTaskId = INVALID_TASK_ID
- }
-
+ @VisibleForTesting
val stateCallback = object : TaskView.Listener {
override fun onInitialized() {
taskViewContainer.apply {
@@ -98,33 +92,29 @@ class DetailDialog(
activityContext,
0 /* enterResId */,
0 /* exitResId */
- ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ ).apply {
+ pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ taskAlwaysOnTop = true
+ }
taskView.startActivity(
pendingIntent,
fillInIntent,
options,
- getTaskViewBounds()
+ taskView.boundsOnScreen,
)
}
override fun onTaskRemovalStarted(taskId: Int) {
- detailTaskId = INVALID_TASK_ID
- dismiss()
+ taskView.release()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
- detailTaskId = taskId
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
setAlpha(1f)
}
}
-
- override fun onReleased() {
- removeDetailTask()
- }
-
override fun onBackPressedOnTaskRoot(taskId: Int) {
dismiss()
}
@@ -138,6 +128,9 @@ class DetailDialog(
setContentView(R.layout.controls_detail_dialog)
taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container)
+ controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply {
+ setOnClickListener { _: View -> dismiss() }
+ }
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
addView(taskView)
@@ -147,13 +140,9 @@ class DetailDialog(
requireViewById<ImageView>(R.id.control_detail_close).apply {
setOnClickListener { _: View -> dismiss() }
}
- requireViewById<View>(R.id.control_detail_root).apply {
- setOnClickListener { _: View -> dismiss() }
- }
requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
setOnClickListener { v: View ->
- removeDetailTask()
dismiss()
val action = ActivityStarter.OnDismissAction {
@@ -201,26 +190,9 @@ class DetailDialog(
taskView.setListener(cvh.uiExecutor, stateCallback)
}
- fun getTaskViewBounds(): Rect {
- val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
- val windowMetrics = wm.getCurrentWindowMetrics()
- val rect = windowMetrics.bounds
- val metricInsets = windowMetrics.windowInsets
- val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars()
- or Type.displayCutout())
- val headerHeight = context.resources.getDimensionPixelSize(
- R.dimen.controls_detail_dialog_header_height)
-
- val finalRect = Rect(rect.left - insets.left /* left */,
- rect.top + insets.top + headerHeight /* top */,
- rect.right - insets.right /* right */,
- rect.bottom - insets.bottom /* bottom */)
- return finalRect
- }
-
override fun dismiss() {
if (!isShowing()) return
- taskView.release()
+ taskView.removeTask()
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 1b0d03276415..848c78644659 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -18,7 +18,6 @@
package com.android.systemui.controls.ui
import android.app.ActivityOptions
-import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
@@ -45,8 +44,6 @@ class PanelTaskViewController(
taskView.alpha = 0f
}
- private var detailTaskId = INVALID_TASK_ID
-
private val fillInIntent =
Intent().apply {
// Apply flags to make behaviour match documentLaunchMode=always.
@@ -57,7 +54,6 @@ class PanelTaskViewController(
private val stateCallback =
object : TaskView.Listener {
override fun onInitialized() {
-
val options =
ActivityOptions.makeCustomAnimation(
activityContext,
@@ -88,12 +84,10 @@ class PanelTaskViewController(
}
override fun onTaskRemovalStarted(taskId: Int) {
- detailTaskId = INVALID_TASK_ID
release()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
- detailTaskId = taskId
taskView.alpha = 1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index da5e933e947a..04a9cae31382 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -25,6 +25,7 @@ import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
+import android.app.IUriGrantsManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -688,4 +689,12 @@ public class FrameworkServicesModule {
static StatusBarManager provideStatusBarManager(Context context) {
return context.getSystemService(StatusBarManager.class);
}
+
+ @Provides
+ @Singleton
+ static IUriGrantsManager provideIUriGrantsManager() {
+ return IUriGrantsManager.Stub.asInterface(
+ ServiceManager.getService(Context.URI_GRANTS_SERVICE)
+ );
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 283a07b206b9..4b6ad6d9be03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@ import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
@@ -173,6 +174,7 @@ import javax.inject.Named;
ControlsModule.class,
CoroutinesModule.class,
DemoModeModule.class,
+ DeviceEntryModule.class,
DisableFlagsModule.class,
DisplayModule.class,
DreamModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
new file mode 100644
index 000000000000..e7f835f7b858
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.deviceentry
+
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ DeviceEntryRepositoryModule::class,
+ ],
+)
+object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
new file mode 100644
index 000000000000..5b85ad01301b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.UserRepository
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Interface for classes that can access device-entry-related application state. */
+interface DeviceEntryRepository {
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method, unless in cases when the current
+ * authentication method is not "secure" (for example, None); in such cases, the value of this
+ * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
+ * by the user to proceed.
+ */
+ val isUnlocked: StateFlow<Boolean>
+
+ /**
+ * Whether the lockscreen should be shown when the authentication method is not secure (e.g.
+ * `None` or `Swipe`).
+ */
+ suspend fun isInsecureLockscreenEnabled(): Boolean
+
+ /**
+ * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
+ * dismissed once the authentication challenge is completed.
+ *
+ * This is a setting that is specific to the face unlock authentication method, because the user
+ * intent to unlock is not known. On devices that don't support face unlock, this always returns
+ * `true`.
+ *
+ * When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss
+ * the lockscreen.
+ */
+ fun isBypassEnabled(): Boolean
+}
+
+/** Encapsulates application state for device entry. */
+@SysUISingleton
+class DeviceEntryRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardBypassController: KeyguardBypassController,
+ keyguardStateController: KeyguardStateController,
+) : DeviceEntryRepository {
+
+ override val isUnlocked =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isUnlocked due to onUnlockedChanged"
+ )
+ }
+
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isUnlocked due to onKeyguardShowingChanged"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "initial isKeyguardUnlocked"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .stateIn(
+ applicationScope,
+ SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ override suspend fun isInsecureLockscreenEnabled(): Boolean {
+ return withContext(backgroundDispatcher) {
+ val selectedUserId = userRepository.getSelectedUserInfo().id
+ !lockPatternUtils.isLockScreenDisabled(selectedUserId)
+ }
+ }
+
+ override fun isBypassEnabled() = keyguardBypassController.bypassEnabled
+
+ companion object {
+ private const val TAG = "DeviceEntryRepositoryImpl"
+ }
+}
+
+@Module
+interface DeviceEntryRepositoryModule {
+ @Binds fun repository(impl: DeviceEntryRepositoryImpl): DeviceEntryRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
new file mode 100644
index 000000000000..5612c9a488ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -0,0 +1,110 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Hosts application business logic related to device entry.
+ *
+ * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless
+ * of the authentication method used.
+ */
+@SysUISingleton
+class DeviceEntryInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val repository: DeviceEntryRepository,
+ private val authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+) {
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method, unless in cases when the current
+ * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+ * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+ * dismissed by the user to proceed.
+ */
+ val isUnlocked: StateFlow<Boolean> =
+ combine(
+ repository.isUnlocked,
+ authenticationInteractor.authenticationMethod,
+ ) { isUnlocked, authenticationMethod ->
+ !authenticationMethod.isSecure || isUnlocked
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ /**
+ * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
+ * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away
+ * the non-secure lockscreen, even though they've already authenticated.
+ *
+ * Note: This does not imply that the lockscreen is visible or not.
+ */
+ val isDeviceEntered: StateFlow<Boolean> =
+ sceneInteractor.desiredScene
+ .map { it.key }
+ .filter { currentScene ->
+ currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+ }
+ .map { it == SceneKey.Gone }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Whether it's currently possible to swipe up to enter the device without requiring
+ * authentication. This returns `false` whenever the lockscreen has been dismissed.
+ *
+ * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+ * UI.
+ */
+ val canSwipeToEnter =
+ combine(authenticationInteractor.authenticationMethod, isDeviceEntered) {
+ authenticationMethod,
+ isDeviceEntered ->
+ authenticationMethod is AuthenticationMethodModel.Swipe && !isDeviceEntered
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Returns `true` if the device currently requires authentication before entry is granted;
+ * `false` if the device can be entered without authenticating first.
+ */
+ suspend fun isAuthenticationRequired(): Boolean {
+ return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure
+ }
+
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismissed once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled() = repository.isBypassEnabled()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ede0339a9539..10f1d7c025e7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -80,12 +80,6 @@ object Flags {
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
- /** Makes sure notification panel is updated before the user switch is complete. */
- // TODO(b/278873737): Tracking Bug
- @JvmField
- val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
- releasedFlag("load_notifications_before_the_user_switch_is_complete")
-
// TODO(b/277338665): Tracking Bug
@JvmField
val NOTIFICATION_SHELF_REFACTOR =
@@ -303,6 +297,11 @@ object Flags {
@JvmField val MIGRATE_CLOCKS_TO_BLUEPRINT =
unreleasedFlag("migrate_clocks_to_blueprint")
+ /** Migrate KeyguardRootView to use composables. */
+ // TODO(b/301969856): Tracking Bug.
+ @JvmField val KEYGUARD_ROOT_VIEW_USE_COMPOSE =
+ unreleasedFlag("keyguard_root_view_use_compose")
+
/** Enables preview loading animation in the wallpaper picker. */
// TODO(b/274443705): Tracking Bug
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index fb02c7d68a9f..1761ca86f588 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -26,7 +26,6 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -40,7 +39,9 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -69,6 +70,7 @@ constructor(
private val context: Context,
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: LockIconViewController,
+ private val shadeInteractor: ShadeInteractor,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -135,6 +137,7 @@ constructor(
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
keyguardStateController,
+ shadeInteractor
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2557e8112e1e..36b93cdf6217 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -47,7 +47,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -97,9 +96,6 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
- /** Is the keyguard in a unlocked state? */
- val isKeyguardUnlocked: StateFlow<Boolean>
-
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -206,14 +202,6 @@ interface KeyguardRepository {
*/
fun isKeyguardShowing(): Boolean
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismissed once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- fun isBypassEnabled(): Boolean
-
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
@@ -265,7 +253,6 @@ constructor(
screenLifecycle: ScreenLifecycle,
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
- private val keyguardBypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val dozeParameters: DozeParameters,
@@ -370,44 +357,6 @@ constructor(
}
.distinctUntilChanged()
- override val isKeyguardUnlocked: StateFlow<Boolean> =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardStateController.Callback {
- override fun onUnlockedChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isKeyguardUnlocked due to onUnlockedChanged"
- )
- }
-
- override fun onKeyguardShowingChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
- )
- }
- }
-
- keyguardStateController.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "initial isKeyguardUnlocked"
- )
-
- awaitClose { keyguardStateController.removeCallback(callback) }
- }
- .distinctUntilChanged()
- .stateIn(
- scope,
- SharingStarted.Eagerly,
- initialValue = false,
- )
-
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -543,10 +492,6 @@ constructor(
return keyguardStateController.isShowing
}
- override fun isBypassEnabled(): Boolean {
- return keyguardBypassController.bypassEnabled
- }
-
// TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
// [SceneInteractor] when scenes are ready.
override val statusBarState: StateFlow<StatusBarState> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 338f9945f5db..80634682c8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,7 +23,6 @@ import android.app.StatusBarManager
import android.graphics.Point
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.systemui.res.R
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -31,6 +30,7 @@ import com.android.systemui.common.shared.model.Position
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -40,10 +40,10 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
@@ -79,6 +79,7 @@ constructor(
private val commandQueue: CommandQueue,
featureFlags: FeatureFlags,
sceneContainerFlags: SceneContainerFlags,
+ deviceEntryRepository: DeviceEntryRepository,
bouncerRepository: KeyguardBouncerRepository,
configurationRepository: ConfigurationRepository,
shadeRepository: ShadeRepository,
@@ -168,7 +169,7 @@ constructor(
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is unlocked or not. */
- val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+ val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -323,26 +324,7 @@ constructor(
repository.setAnimateDozingTransitions(animate)
}
- fun isKeyguardDismissable(): Boolean {
- return repository.isKeyguardUnlocked.value
- }
-
companion object {
private const val TAG = "KeyguardInteractor"
-
- fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> true
- KeyguardState.DOZING -> true
- KeyguardState.DREAMING -> true
- KeyguardState.AOD -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> false
- KeyguardState.OCCLUDED -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
- }
- }
}
}
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 f564d0073e42..053727ace76d 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
@@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.ViewPriority
@@ -55,6 +56,7 @@ object KeyguardRootViewBinder {
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
chipbarCoordinator: ChipbarCoordinator,
keyguardStateController: KeyguardStateController,
+ shadeInteractor: ShadeInteractor,
): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
@@ -88,6 +90,17 @@ object KeyguardRootViewBinder {
}
}
}
+
+ launch {
+ shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
+ view.visibility =
+ if (isFullyAnyExpanded) {
+ View.INVISIBLE
+ } else {
+ View.VISIBLE
+ }
+ }
+ }
}
repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 2ad74fbc6674..864e345e9c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -62,6 +62,7 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage
import com.android.systemui.monet.ColorScheme
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.DefaultClockController
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -109,6 +110,7 @@ constructor(
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardStateController: KeyguardStateController,
+ private val shadeInteractor: ShadeInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -317,6 +319,7 @@ constructor(
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
keyguardStateController,
+ shadeInteractor,
)
)
rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
index 9409036586e8..f4bc7137b50c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
@@ -29,11 +29,11 @@ import androidx.constraintlayout.widget.ConstraintSet
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import javax.inject.Inject
@@ -73,11 +73,11 @@ constructor(
val mBottomPaddingPx =
context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
val bounds = windowManager.currentWindowMetrics.bounds
- val insets = windowManager.currentWindowMetrics.windowInsets
var widthPixels = bounds.right.toFloat()
if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
// Assumed to be initially neglected as there are no left or right insets in portrait.
// However, on landscape, these insets need to included when calculating the midpoint.
+ val insets = windowManager.currentWindowMetrics.windowInsets
widthPixels -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
}
val heightPixels = bounds.bottom.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 91b33572345d..c03e4d950cae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,26 +34,22 @@ class LockscreenSceneViewModel
@Inject
constructor(
@Application applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
communalInteractor: CommunalInteractor,
val longPress: KeyguardLongPressViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
- authenticationInteractor.isUnlocked
+ deviceEntryInteractor.isUnlocked
.map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(authenticationInteractor.isUnlocked.value),
+ initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
)
private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) {
- SceneKey.Gone
- } else {
- SceneKey.Bouncer
- }
+ return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer
}
/** The key of the scene we should switch to when swiping left. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index ae3c912d6d1b..ed6d41e5a75b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -101,7 +101,8 @@ constructor(
panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val logger: MediaViewLogger,
) {
/** Track the media player setting status on lock screen. */
@@ -1057,6 +1058,7 @@ constructor(
// that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
}
+ logger.logMediaHostAttachment(currentAttachmentLocation)
if (isCrossFadeAnimatorRunning) {
// When cross-fading with an animation, we only notify the media carousel of the
// location change, once the view is reattached to the new place and not
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index 8f1595d7d7a2..3ff2315956ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -52,4 +52,8 @@ class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogB
{ "location ($str1): $int1 -> $int2" }
)
}
+
+ fun logMediaHostAttachment(host: Int) {
+ buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a9626d7c7a0..10f95e0b7201 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -48,6 +48,7 @@ import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
@@ -56,6 +57,8 @@ import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.settings.SecureSettings;
+import dagger.Lazy;
+
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
@@ -121,6 +124,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
@Inject
public QSTileHost(Context context,
+ Lazy<NewQSTileFactory> newQsTileFactoryProvider,
QSFactory defaultFactory,
@Main Executor mainExecutor,
PluginManager pluginManager,
@@ -147,6 +151,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
mShadeController = shadeController;
+ if (featureFlags.getPipelineTilesEnabled()) {
+ mQsFactories.add(newQsTileFactoryProvider.get());
+ }
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
mUserTracker = userTracker;
@@ -326,7 +333,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
try {
tile = createTile(tileSpec);
if (tile != null) {
- tile.setTileSpec(tileSpec);
if (tile.isAvailable()) {
newTiles.put(tileSpec, tile);
mQSLogger.logTileAdded(tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index a6226b36b0fd..2af7ae0614ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -130,11 +130,9 @@ public class TileQueryHelper {
if (tile == null) {
continue;
} else if (!tile.isAvailable()) {
- tile.setTileSpec(spec);
tile.destroy();
continue;
}
- tile.setTileSpec(spec);
tilesToAdd.add(tile);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 92490e8fbd43..a65967a0349b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -31,6 +31,7 @@ import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.external.QSExternalModule;
import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
@@ -41,14 +42,14 @@ import com.android.systemui.statusbar.policy.SafetyController;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
-import java.util.Map;
-
-import javax.inject.Named;
-
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
+import java.util.Map;
+
+import javax.inject.Named;
+
/**
* Module for QS dependencies
*/
@@ -68,6 +69,11 @@ public interface QSModule {
@Multibinds
Map<String, QSTileImpl<?>> tileMap();
+ /** A map of internal QS tile ViewModels. Ensures that this can be injected even if
+ * it is empty */
+ @Multibinds
+ Map<String, QSTileViewModel> tileViewModelMap();
+
@Provides
@SysUISingleton
static AutoTileManager provideAutoTileManager(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 34d6233deddb..128c23745e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -17,6 +17,7 @@ package com.android.systemui.qs.external;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import android.app.IUriGrantsManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -31,6 +32,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.provider.Settings;
import android.service.quicksettings.IQSTileService;
@@ -43,11 +45,9 @@ import android.view.View;
import android.view.WindowManagerGlobal;
import android.widget.Switch;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -67,9 +67,10 @@ import com.android.systemui.settings.DisplayTracker;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
@@ -109,24 +110,30 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false);
private final TileServices mTileServices;
- private CustomTile(
- QSHost host,
+ private int mServiceUid = Process.INVALID_UID;
+
+ private final IUriGrantsManager mIUriGrantsManager;
+
+ @AssistedInject
+ CustomTile(
+ Lazy<QSHost> host,
QsEventLogger uiEventLogger,
- Looper backgroundLooper,
- Handler mainHandler,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- String action,
- Context userContext,
+ @Assisted String action,
+ @Assisted Context userContext,
CustomTileStatePersister customTileStatePersister,
TileServices tileServices,
- DisplayTracker displayTracker
+ DisplayTracker displayTracker,
+ IUriGrantsManager uriGrantsManager
) {
- super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
+ super(host.get(), uiEventLogger, backgroundLooper, mainHandler, falsingManager,
+ metricsLogger, statusBarStateController, activityStarter, qsLogger);
mTileServices = tileServices;
mWindowManager = WindowManagerGlobal.getWindowManagerService();
mComponent = ComponentName.unflattenFromString(action);
@@ -139,6 +146,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
mService = mServiceManager.getTileService();
mCustomTileStatePersister = customTileStatePersister;
mDisplayTracker = displayTracker;
+ mIUriGrantsManager = uriGrantsManager;
}
@Override
@@ -268,7 +276,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
*
* @param tile tile populated with state to apply
*/
- public void updateTileState(Tile tile) {
+ public void updateTileState(Tile tile, int appUid) {
+ mServiceUid = appUid;
// This comes from a binder call IQSService.updateQsTile
mHandler.post(() -> handleUpdateTileState(tile));
}
@@ -433,14 +442,25 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
state.state = tileState;
Drawable drawable = null;
try {
- drawable = mTile.getIcon().loadDrawable(mUserContext);
+ drawable = mTile.getIcon().loadDrawableCheckingUriGrant(
+ mUserContext,
+ mIUriGrantsManager,
+ mServiceUid,
+ mComponent.getPackageName()
+ );
} catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state");
state.state = Tile.STATE_UNAVAILABLE;
- drawable = mDefaultIcon.loadDrawable(mUserContext);
}
- final Drawable drawableF = drawable;
+ final Drawable drawableF;
+ if (drawable != null) {
+ drawableF = drawable;
+ } else if (mDefaultIcon != null) {
+ drawableF = mDefaultIcon.loadDrawable(mUserContext);
+ } else {
+ drawableF = null;
+ }
state.iconSupplier = () -> {
if (drawableF == null) return null;
Drawable.ConstantState cs = drawableF.getConstantState();
@@ -543,96 +563,17 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
/**
* Create a {@link CustomTile} for a given spec and user.
*
- * @param builder including injected common dependencies.
+ * @param factory including injected common dependencies.
* @param spec as provided by {@link CustomTile#toSpec}
* @param userContext context for the user that is creating this tile.
* @return a new {@link CustomTile}
*/
- public static CustomTile create(Builder builder, String spec, Context userContext) {
- return builder
- .setSpec(spec)
- .setUserContext(userContext)
- .build();
- }
-
- public static class Builder {
- final Lazy<QSHost> mQSHostLazy;
- final QsEventLogger mUiEventLogger;
- final Looper mBackgroundLooper;
- final Handler mMainHandler;
- private final FalsingManager mFalsingManager;
- final MetricsLogger mMetricsLogger;
- final StatusBarStateController mStatusBarStateController;
- final ActivityStarter mActivityStarter;
- final QSLogger mQSLogger;
- final CustomTileStatePersister mCustomTileStatePersister;
- private TileServices mTileServices;
- final DisplayTracker mDisplayTracker;
-
- Context mUserContext;
- String mSpec = "";
-
- @Inject
- public Builder(
- Lazy<QSHost> hostLazy,
- QsEventLogger uiEventLogger,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- CustomTileStatePersister customTileStatePersister,
- TileServices tileServices,
- DisplayTracker displayTracker
- ) {
- mQSHostLazy = hostLazy;
- mUiEventLogger = uiEventLogger;
- mBackgroundLooper = backgroundLooper;
- mMainHandler = mainHandler;
- mFalsingManager = falsingManager;
- mMetricsLogger = metricsLogger;
- mStatusBarStateController = statusBarStateController;
- mActivityStarter = activityStarter;
- mQSLogger = qsLogger;
- mCustomTileStatePersister = customTileStatePersister;
- mTileServices = tileServices;
- mDisplayTracker = displayTracker;
- }
-
- Builder setSpec(@NonNull String spec) {
- mSpec = spec;
- return this;
- }
-
- Builder setUserContext(@NonNull Context userContext) {
- mUserContext = userContext;
- return this;
- }
-
- @VisibleForTesting
- public CustomTile build() {
- if (mUserContext == null) {
- throw new NullPointerException("UserContext cannot be null");
- }
- String action = getAction(mSpec);
- return new CustomTile(
- mQSHostLazy.get(),
- mUiEventLogger,
- mBackgroundLooper,
- mMainHandler,
- mFalsingManager,
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQSLogger,
- action,
- mUserContext,
- mCustomTileStatePersister,
- mTileServices,
- mDisplayTracker
- );
- }
+ public static CustomTile create(Factory factory, String spec, Context userContext) {
+ return factory.create(getAction(spec), userContext);
+ }
+
+ @AssistedFactory
+ public interface Factory {
+ CustomTile create(String action, Context userContext);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 1659c3e673ef..c3c587de5a24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.content.Context
import android.graphics.drawable.Icon
import android.view.ContextThemeWrapper
@@ -34,7 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
* Dialog to present to the user to ask for authorization to add a [TileService].
*/
class TileRequestDialog(
- context: Context
+ context: Context,
) : SystemUIDialog(context) {
companion object {
@@ -44,7 +45,7 @@ class TileRequestDialog(
/**
* Set the data of the tile to add, to show the user.
*/
- fun setTileData(tileData: TileData) {
+ fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
val ll = (LayoutInflater
.from(context)
.inflate(R.layout.tile_service_request_dialog, null)
@@ -54,7 +55,7 @@ class TileRequestDialog(
.getString(R.string.qs_tile_request_dialog_text, tileData.appName)
}
addView(
- createTileView(tileData),
+ createTileView(tileData, iUriGrantsManager),
context.resources.getDimensionPixelSize(
R.dimen.qs_tile_service_request_tile_width),
context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
@@ -65,13 +66,21 @@ class TileRequestDialog(
setView(ll, spacing, spacing, spacing, spacing / 2)
}
- private fun createTileView(tileData: TileData): QSTileView {
+ private fun createTileView(
+ tileData: TileData,
+ iUriGrantsManager: IUriGrantsManager,
+ ): QSTileView {
val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
val tile = QSTileViewImpl(themedContext, true)
val state = QSTile.BooleanState().apply {
label = tileData.label
handlesLongClick = false
- icon = tileData.icon?.loadDrawable(context)?.let {
+ icon = tileData.icon?.loadDrawableCheckingUriGrant(
+ context,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )?.let {
QSTileImpl.DrawableIcon(it)
} ?: ResourceIcon.get(R.drawable.android)
contentDescription = label
@@ -93,8 +102,10 @@ class TileRequestDialog(
* @property icon Icon for the tile.
*/
data class TileData(
+ val callingUid: Int,
val appName: CharSequence,
val label: CharSequence,
- val icon: Icon?
+ val icon: Icon?,
+ val packageName: String,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 899d0e2a4e35..08567afd729e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.external
import android.app.Dialog
+import android.app.IUriGrantsManager
import android.app.StatusBarManager
import android.content.ComponentName
import android.content.DialogInterface
@@ -42,12 +43,13 @@ private const val TAG = "TileServiceRequestController"
/**
* Controller to interface between [TileRequestDialog] and [QSHost].
*/
-class TileServiceRequestController constructor(
- private val qsHost: QSHost,
- private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry,
- private val eventLogger: TileRequestDialogEventLogger,
- private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+class TileServiceRequestController(
+ private val qsHost: QSHost,
+ private val commandQueue: CommandQueue,
+ private val commandRegistry: CommandRegistry,
+ private val eventLogger: TileRequestDialogEventLogger,
+ private val iUriGrantsManager: IUriGrantsManager,
+ private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
) {
companion object {
@@ -62,13 +64,14 @@ class TileServiceRequestController constructor(
private val commandQueueCallback = object : CommandQueue.Callbacks {
override fun requestAddTile(
+ callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
icon: Icon,
callback: IAddTileResultCallback
) {
- requestTileAdd(componentName, appName, label, icon) {
+ requestTileAdd(callingUid, componentName, appName, label, icon) {
try {
callback.onTileRequest(it)
} catch (e: RemoteException) {
@@ -98,6 +101,7 @@ class TileServiceRequestController constructor(
@VisibleForTesting
internal fun requestTileAdd(
+ callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
@@ -119,7 +123,13 @@ class TileServiceRequestController constructor(
eventLogger.logUserResponse(response, packageName, instanceId)
callback.accept(response)
}
- val tileData = TileRequestDialog.TileData(appName, label, icon)
+ val tileData = TileRequestDialog.TileData(
+ callingUid,
+ appName,
+ label,
+ icon,
+ componentName.packageName,
+ )
createDialog(tileData, dialogResponse).also { dialog ->
dialogCanceller = {
if (packageName == it) {
@@ -143,7 +153,7 @@ class TileServiceRequestController constructor(
}
}
return dialogCreator().apply {
- setTileData(tileData)
+ setTileData(tileData, iUriGrantsManager)
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
setOnCancelListener { responseHandler.accept(DISMISSED) }
@@ -168,7 +178,7 @@ class TileServiceRequestController constructor(
Log.w(TAG, "Malformed componentName ${args[0]}")
return
}
- requestTileAdd(componentName, args[1], args[2], null) {
+ requestTileAdd(0, componentName, args[1], args[2], null) {
Log.d(TAG, "Response: $it")
}
}
@@ -192,14 +202,16 @@ class TileServiceRequestController constructor(
@SysUISingleton
class Builder @Inject constructor(
private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry
+ private val commandRegistry: CommandRegistry,
+ private val iUriGrantsManager: IUriGrantsManager,
) {
fun create(qsHost: QSHost): TileServiceRequestController {
return TileServiceRequestController(
qsHost,
commandQueue,
commandRegistry,
- TileRequestDialogEventLogger()
+ TileRequestDialogEventLogger(),
+ iUriGrantsManager,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index c8691acfd278..fc2402258009 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -184,7 +184,7 @@ public class TileServices extends IQSService.Stub {
}
}
- private void verifyCaller(CustomTile tile) {
+ private int verifyCaller(CustomTile tile) {
try {
String packageName = tile.getComponent().getPackageName();
int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
@@ -192,6 +192,7 @@ public class TileServices extends IQSService.Stub {
if (Binder.getCallingUid() != uid) {
throw new SecurityException("Component outside caller's uid");
}
+ return uid;
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException(e);
}
@@ -228,7 +229,7 @@ public class TileServices extends IQSService.Stub {
public void updateQsTile(Tile tile, IBinder token) {
CustomTile customTile = getTileForToken(token);
if (customTile != null) {
- verifyCaller(customTile);
+ int uid = verifyCaller(customTile);
synchronized (mServices) {
final TileServiceManager tileServiceManager = mServices.get(customTile);
if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) {
@@ -239,7 +240,7 @@ public class TileServices extends IQSService.Stub {
tileServiceManager.clearPendingBind();
tileServiceManager.setLastUpdate(System.currentTimeMillis());
}
- customTile.updateTileState(tile);
+ customTile.updateTileState(tile, uid);
customTile.refreshState();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 21aaa94f9e10..b50798e59953 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -32,6 +32,8 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractorImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -61,6 +63,11 @@ abstract class QSPipelineModule {
): InstalledTilesComponentRepository
@Binds
+ abstract fun provideDisabledByPolicyInteractor(
+ impl: DisabledByPolicyInteractorImpl
+ ): DisabledByPolicyInteractor
+
+ @Binds
@IntoMap
@ClassKey(QSPipelineCoreStartable::class)
abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 00c23582f726..c5512c15ccc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -41,10 +41,12 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.qs.toProto
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
+import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -131,6 +133,7 @@ constructor(
private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
private val customTileStatePersister: CustomTileStatePersister,
+ private val newQSTileFactory: Lazy<NewQSTileFactory>,
private val tileFactory: QSFactory,
private val customTileAddedRepository: CustomTileAddedRepository,
private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
@@ -139,7 +142,7 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
private val logger: QSPipelineLogger,
- featureFlags: QSPipelineFlagsRepository,
+ private val featureFlags: QSPipelineFlagsRepository,
) : CurrentTilesInteractor {
private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
@@ -333,12 +336,19 @@ constructor(
}
private suspend fun createTile(spec: TileSpec): QSTile? {
- val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+ val tile =
+ withContext(mainDispatcher) {
+ if (featureFlags.pipelineTilesEnabled) {
+ newQSTileFactory.get().createTile(spec.spec)
+ } else {
+ null
+ }
+ ?: tileFactory.createTile(spec.spec)
+ }
if (tile == null) {
logger.logTileNotFoundInFactory(spec)
return null
} else {
- tile.tileSpec = spec.spec
return if (!tile.isAvailable) {
logger.logTileDestroyed(
spec,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 551b0f4890a4..1a71b715fe3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -1,7 +1,7 @@
package com.android.systemui.qs.pipeline.shared
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import javax.inject.Inject
@@ -10,7 +10,7 @@ import javax.inject.Inject
class QSPipelineFlagsRepository
@Inject
constructor(
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
) {
/** @see Flags.QS_PIPELINE_NEW_HOST */
@@ -20,4 +20,8 @@ constructor(
/** @see Flags.QS_PIPELINE_AUTO_ADD */
val pipelineAutoAddEnabled: Boolean
get() = pipelineHostEnabled && featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+
+ /** @see Flags.QS_PIPELINE_NEW_TILES */
+ val pipelineTilesEnabled: Boolean
+ get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index 11b5dd7cb036..aed08f8b5457 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -31,11 +31,7 @@ import com.android.systemui.qs.external.CustomTile
sealed class TileSpec private constructor(open val spec: String) {
/** Represents a spec that couldn't be parsed into a valid type of tile. */
- object Invalid : TileSpec("") {
- override fun toString(): String {
- return "TileSpec.INVALID"
- }
- }
+ data object Invalid : TileSpec("")
/** Container for the spec of a tile provided by SystemUI. */
data class PlatformTileSpec
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9c7a73412518..38bbce45e143 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -51,15 +51,15 @@ public class QSFactoryImpl implements QSFactory {
protected final Map<String, Provider<QSTileImpl<?>>> mTileMap;
private final Lazy<QSHost> mQsHostLazy;
- private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
+ private final Provider<CustomTile.Factory> mCustomTileFactoryProvider;
@Inject
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
- Provider<CustomTile.Builder> customTileBuilderProvider,
+ Provider<CustomTile.Factory> customTileFactoryProvider,
Map<String, Provider<QSTileImpl<?>>> tileMap) {
mQsHostLazy = qsHostLazy;
- mCustomTileBuilderProvider = customTileBuilderProvider;
+ mCustomTileFactoryProvider = customTileFactoryProvider;
mTileMap = tileMap;
}
@@ -70,6 +70,7 @@ public class QSFactoryImpl implements QSFactory {
if (tile != null) {
tile.initialize();
tile.postStale(); // Tile was just created, must be stale.
+ tile.setTileSpec(tileSpec);
}
return tile;
}
@@ -86,7 +87,7 @@ public class QSFactoryImpl implements QSFactory {
// Custom tiles
if (tileSpec.startsWith(CustomTile.PREFIX)) {
return CustomTile.create(
- mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
+ mCustomTileFactoryProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
// Broken tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
index e9f907c4d8e7..9d100728e643 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -1,11 +1,27 @@
+/*
+ * 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.qs.tiles.base.actions
import android.content.Intent
+import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import javax.inject.Inject
/**
@@ -17,9 +33,9 @@ class QSTileIntentUserActionHandler
@Inject
constructor(private val activityStarter: ActivityStarter) {
- fun handle(userAction: QSTileUserAction, intent: Intent) {
+ fun handle(view: View?, intent: Intent) {
val animationController: ActivityLaunchAnimator.Controller? =
- userAction.view?.let {
+ view?.let {
ActivityLaunchAnimator.Controller.fromView(
it,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
new file mode 100644
index 000000000000..056f967b09bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.qs.tiles.base.interactor
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Provides restrictions data for the tiles. This is used in
+ * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is
+ * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy].
+ */
+interface DisabledByPolicyInteractor {
+
+ /**
+ * Checks if the tile is restricted by the policy for a specific user. Pass the result to the
+ * [handlePolicyResult] to let the user know that the tile is disable by the admin.
+ */
+ suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult
+
+ /**
+ * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this
+ * method. No further handling is required and the input event can be skipped at this point.
+ *
+ * Returns false when [policyResult] is [PolicyResult.TileEnabled] and this method has done
+ * nothing.
+ */
+ fun handlePolicyResult(policyResult: PolicyResult): Boolean
+
+ sealed interface PolicyResult {
+ /** Tile has no policy restrictions. */
+ data object TileEnabled : PolicyResult
+
+ /**
+ * Tile is disabled by policy. Pass this to [DisabledByPolicyInteractor.handlePolicyResult]
+ * to show the user info screen using
+ * [RestrictedLockUtils.getShowAdminSupportDetailsIntent].
+ */
+ data class TileDisabled(val admin: EnforcedAdmin) : PolicyResult
+ }
+}
+
+@SysUISingleton
+class DisabledByPolicyInteractorImpl
+@Inject
+constructor(
+ private val context: Context,
+ private val activityStarter: ActivityStarter,
+ private val restrictedLockProxy: RestrictedLockProxy,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : DisabledByPolicyInteractor {
+
+ override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult =
+ withContext(backgroundDispatcher) {
+ val admin: EnforcedAdmin =
+ restrictedLockProxy.getEnforcedAdmin(userId, userRestriction)
+ ?: return@withContext PolicyResult.TileEnabled
+
+ return@withContext if (
+ !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction)
+ ) {
+ PolicyResult.TileDisabled(admin)
+ } else {
+ PolicyResult.TileEnabled
+ }
+ }
+
+ override fun handlePolicyResult(policyResult: PolicyResult): Boolean =
+ when (policyResult) {
+ is PolicyResult.TileEnabled -> false
+ is PolicyResult.TileDisabled -> {
+ val intent =
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+ context,
+ policyResult.admin
+ )
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ true
+ }
+ }
+}
+
+/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */
+@VisibleForTesting
+class RestrictedLockProxy @Inject constructor(private val context: Context) {
+
+ @WorkerThread
+ fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean =
+ RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context,
+ userRestriction,
+ userId,
+ )
+
+ @WorkerThread
+ fun getEnforcedAdmin(userId: Int, userRestriction: String?): EnforcedAdmin? =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context,
+ userRestriction,
+ userId,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
index 1a03481b9fca..7a22e3cf8bc8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
index 82897044f06c..0aa6b0be5485 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
data class QSTileDataRequest(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
index d6c9705a6aa1..2bc664311644 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import androidx.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 8569fc73adb4..14fc639c8aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import android.annotation.WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
index ed7ec8e32de4..ffe38ddacfda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index c2a75fac60f5..58a335e462a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -1,8 +1,26 @@
+/*
+ * 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.qs.tiles.base.viewmodel
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import com.android.internal.util.Preconditions
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -10,10 +28,13 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.util.kotlin.sample
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@@ -25,6 +46,7 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -37,40 +59,35 @@ import kotlinx.coroutines.flow.stateIn
* Provides a hassle-free way to implement new tiles according to current System UI architecture
* standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
* [QSTileLifecycle.ALIVE] state.
+ *
+ * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
*/
-abstract class BaseQSTileViewModel<DATA_TYPE>
+class BaseQSTileViewModel<DATA_TYPE>
@VisibleForTesting
constructor(
- final override val config: QSTileConfig,
+ override val config: QSTileConfig,
private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val tileScope: CoroutineScope,
) : QSTileViewModel {
- /**
- * @param config contains all the static information (like TileSpec) about the tile.
- * @param userActionInteractor encapsulates user input processing logic. Use it to start
- * activities, show dialogs or otherwise update the tile state.
- * @param tileDataInteractor provides [DATA_TYPE] and its availability.
- * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call
- * interactors methods. This should likely to be @Background CoroutineDispatcher.
- * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer.
- * It's called in [backgroundDispatcher], so it's safe to perform long running operations
- * there.
- */
+ @AssistedInject
constructor(
- config: QSTileConfig,
- userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
- tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
- mapper: QSTileDataToStateMapper<DATA_TYPE>,
- backgroundDispatcher: CoroutineDispatcher,
+ @Assisted config: QSTileConfig,
+ @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+ @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+ @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ @Background backgroundDispatcher: CoroutineDispatcher,
) : this(
config,
userActionInteractor,
tileDataInteractor,
mapper,
+ disabledByPolicyInteractor,
backgroundDispatcher,
CoroutineScope(SupervisorJob())
)
@@ -92,7 +109,7 @@ constructor(
.stateIn(
tileScope,
SharingStarted.WhileSubscribed(),
- false,
+ true,
)
private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
@@ -145,7 +162,7 @@ constructor(
userIds
.flatMapLatest { userId ->
merge(
- userInputFlow(),
+ userInputFlow(userId),
forceUpdates.map { StateUpdateTrigger.ForceUpdate },
)
.onStart { emit(StateUpdateTrigger.InitialRequest) }
@@ -172,14 +189,41 @@ constructor(
replay = 1, // we only care about the most recent value
)
- private fun userInputFlow(): Flow<StateUpdateTrigger> {
+ private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
data class StateWithData<T>(val state: QSTileState, val data: T)
+ return when (config.policy) {
+ is QSTilePolicy.NoRestrictions -> userInputs
+ is QSTilePolicy.Restricted ->
+ userInputs.filter {
+ val result =
+ disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
+ !disabledByPolicyInteractor.handlePolicyResult(result)
+ }
// Skip the input until there is some data
- return userInputs.sample(
- state.combine(tileData) { state, data -> StateWithData(state, data) }
- ) { input, stateWithData ->
+ }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
+ input,
+ stateWithData ->
StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
}
}
+
+ interface Factory<T> {
+
+ /**
+ * @param config contains all the static information (like TileSpec) about the tile.
+ * @param userActionInteractor encapsulates user input processing logic. Use it to start
+ * activities, show dialogs or otherwise update the tile state.
+ * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+ * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View
+ * layer. It's called in [backgroundDispatcher], so it's safe to perform long running
+ * operations there.
+ */
+ fun create(
+ config: QSTileConfig,
+ userActionInteractor: QSTileUserActionInteractor<T>,
+ tileDataInteractor: QSTileDataInteractor<T>,
+ mapper: QSTileDataToStateMapper<T>,
+ ): BaseQSTileViewModel<T>
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
new file mode 100644
index 000000000000..d0809c52acd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.qs.tiles.di
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import javax.inject.Inject
+import javax.inject.Provider
+
+// TODO(b/http://b/299909989): Rename the factory after rollout
+@SysUISingleton
+class NewQSTileFactory
+@Inject
+constructor(
+ private val adapterFactory: QSTileViewModelAdapter.Factory,
+ private val tileMap:
+ Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
+) : QSFactory {
+
+ override fun createTile(tileSpec: String): QSTile? =
+ tileMap[tileSpec]?.let {
+ val tile = it.get()
+ tile.onLifecycle(QSTileLifecycle.ALIVE)
+ adapterFactory.create(tile)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index a5eaac154230..1a6cf99ab810 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -1,14 +1,44 @@
+/*
+ * 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.qs.tiles.viewmodel
-import android.graphics.drawable.Icon
+import androidx.annotation.StringRes
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
data class QSTileConfig(
val tileSpec: TileSpec,
val tileIcon: Icon,
- val tileLabel: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
-val instanceId: InstanceId,
- */
+ @StringRes val tileLabelRes: Int,
+ val instanceId: InstanceId,
+ val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
)
+
+/** Represents policy restrictions that may be imposed on the tile. */
+sealed interface QSTilePolicy {
+ /** Tile has no policy restrictions */
+ data object NoRestrictions : QSTilePolicy
+
+ /**
+ * Tile might be disabled by policy. [userRestriction] is usually a constant from
+ * [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE].
+ * [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used
+ * to resolve this and show user a message when needed.
+ */
+ data class Restricted(val userRestriction: String) : QSTilePolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
index 1d5c1bcada1f..6d7c57605bec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.viewmodel
enum class QSTileLifecycle {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 53f9edfb954c..0ccde741e2cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -1,18 +1,112 @@
+/*
+ * 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.qs.tiles.viewmodel
-import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
+/**
+ * Represents current a state of the tile to be displayed in on the view. Consider using
+ * [QSTileState.build] for better state creation experience and preset default values for certain
+ * fields.
+ *
+ * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
+ */
data class QSTileState(
- val icon: Icon,
+ val icon: () -> Icon,
val label: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
- val subtitle: CharSequence = "",
- val activeState: ActivationState = Active,
- val enabledState: Enabled = Enabled,
- val loopIconAnimation: Boolean = false,
- val secondaryIcon: Icon? = null,
- val slashState: SlashState? = null,
- val supportedActions: Collection<UserAction> = listOf(Click), clicks should be a default action
-*/
-)
+ val activationState: ActivationState,
+ val secondaryLabel: CharSequence?,
+ val supportedActions: Set<UserAction>,
+ val contentDescription: CharSequence?,
+ val stateDescription: CharSequence?,
+ val sideViewIcon: SideViewIcon,
+ val enabledState: EnabledState,
+ val expandedAccessibilityClassName: String?,
+) {
+
+ companion object {
+
+ fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+ Builder(icon, label).apply(build).build()
+
+ fun build(icon: Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+ build({ icon }, label, build)
+ }
+
+ enum class ActivationState(val legacyState: Int) {
+ // An unavailable state indicates that for some reason this tile is not currently available
+ // to the user, and will have no click action. The tile's icon will be tinted differently to
+ // reflect this state.
+ UNAVAILABLE(Tile.STATE_UNAVAILABLE),
+ // This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is
+ // on, cast is casting). This is the default state.
+ ACTIVE(Tile.STATE_ACTIVE),
+ // This represents a tile that is currently in a disabled state but is still interactable. A
+ // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
+ // bluetooth disabled), but is still interactable by the user to modify this state.
+ INACTIVE(Tile.STATE_INACTIVE),
+ }
+
+ /**
+ * Enabled tile behaves as usual where is disabled one is frozen and inactive in its current
+ * [ActivationState].
+ */
+ enum class EnabledState {
+ ENABLED,
+ DISABLED,
+ }
+
+ enum class UserAction {
+ CLICK,
+ LONG_CLICK,
+ }
+
+ sealed interface SideViewIcon {
+ data class Custom(val icon: Icon) : SideViewIcon
+ data object Chevron : SideViewIcon
+ data object None : SideViewIcon
+ }
+
+ class Builder(
+ var icon: () -> Icon,
+ var label: CharSequence,
+ ) {
+ var activationState: ActivationState = ActivationState.INACTIVE
+ var secondaryLabel: CharSequence? = null
+ var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
+ var contentDescription: CharSequence? = null
+ var stateDescription: CharSequence? = null
+ var sideViewIcon: SideViewIcon = SideViewIcon.None
+ var enabledState: EnabledState = EnabledState.ENABLED
+ var expandedAccessibilityClassName: String? = null
+
+ fun build(): QSTileState =
+ QSTileState(
+ icon,
+ label,
+ activationState,
+ secondaryLabel,
+ supportedActions,
+ contentDescription,
+ stateDescription,
+ sideViewIcon,
+ enabledState,
+ expandedAccessibilityClassName,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index f1f8f0152c67..a1450420131b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -1,13 +1,27 @@
+/*
+ * 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.qs.tiles.viewmodel
-import android.content.Context
import android.view.View
sealed interface QSTileUserAction {
- val context: Context
val view: View?
- class Click(override val context: Context, override val view: View?) : QSTileUserAction
- class LongClick(override val context: Context, override val view: View?) : QSTileUserAction
+ class Click(override val view: View?) : QSTileUserAction
+ class LongClick(override val view: View?) : QSTileUserAction
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index d66d0a195e02..e5cb7ea3e098 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.viewmodel
import kotlinx.coroutines.flow.SharedFlow
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
new file mode 100644
index 000000000000..f6299e38ae18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.qs.tiles.viewmodel
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import androidx.annotation.GuardedBy
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.function.Supplier
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
+class QSTileViewModelAdapter
+@AssistedInject
+constructor(
+ private val qsHost: QSHost,
+ @Assisted private val qsTileViewModel: QSTileViewModel,
+) : QSTile {
+
+ private val context
+ get() = qsHost.context
+
+ @GuardedBy("callbacks")
+ private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
+ @GuardedBy("listeningClients")
+ private val listeningClients: MutableCollection<Any> = mutableSetOf()
+
+ // Cancels the jobs when the adapter is no longer alive
+ private val adapterScope = CoroutineScope(SupervisorJob())
+ // Cancels the jobs when clients stop listening
+ private val listeningScope = CoroutineScope(SupervisorJob())
+
+ init {
+ adapterScope.launch {
+ qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+ if (!isAvailable) {
+ qsHost.removeTile(tileSpec)
+ }
+ // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
+ // why we only allow isAvailable == true once and throw an exception afterwards.
+ if (index > 0 && isAvailable) {
+ // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
+ // guidance on how to auto add your tile
+ throw UnsupportedOperationException("Turning on tile is not supported now")
+ }
+ }
+ }
+
+ // QSTileHost doesn't call this when userId is initialized
+ userSwitch(qsHost.userId)
+
+ if (DEBUG) {
+ Log.d(TAG, "Using new tiles for: $tileSpec")
+ }
+ }
+
+ override fun isAvailable(): Boolean = qsTileViewModel.isAvailable.value
+
+ override fun setTileSpec(tileSpec: String?) {
+ throw UnsupportedOperationException("Tile spec is immutable in new tiles")
+ }
+
+ override fun refreshState() {
+ qsTileViewModel.forceUpdate()
+ }
+
+ override fun addCallback(callback: QSTile.Callback?) {
+ callback ?: return
+ synchronized(callbacks) { callbacks.add(callback) }
+ }
+
+ override fun removeCallback(callback: QSTile.Callback?) {
+ callback ?: return
+ synchronized(callbacks) { callbacks.remove(callback) }
+ }
+
+ override fun removeCallbacks() {
+ synchronized(callbacks) { callbacks.clear() }
+ }
+
+ override fun click(view: View?) {
+ if (isActionSupported(QSTileState.UserAction.CLICK)) {
+ qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+ }
+ }
+
+ override fun secondaryClick(view: View?) {
+ if (isActionSupported(QSTileState.UserAction.CLICK)) {
+ qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+ }
+ }
+
+ override fun longClick(view: View?) {
+ if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) {
+ qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view))
+ }
+ }
+
+ private fun isActionSupported(action: QSTileState.UserAction): Boolean =
+ qsTileViewModel.currentState?.supportedActions?.contains(action) == true
+
+ override fun userSwitch(currentUser: Int) {
+ qsTileViewModel.onUserIdChanged(currentUser)
+ }
+
+ @Deprecated(
+ "Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec",
+ replaceWith = ReplaceWith("getMetricsSpec"),
+ )
+ override fun getMetricsCategory(): Int = 0
+
+ override fun setListening(client: Any?, listening: Boolean) {
+ client ?: return
+ synchronized(listeningClients) {
+ if (listening) {
+ listeningClients.add(client)
+ if (listeningClients.size == 1) {
+ qsTileViewModel.state
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ synchronized(callbacks) {
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
+ }
+ .launchIn(listeningScope)
+ }
+ } else {
+ listeningClients.remove(client)
+ if (listeningClients.isEmpty()) {
+ listeningScope.coroutineContext.cancelChildren()
+ }
+ }
+ }
+ }
+
+ override fun isListening(): Boolean =
+ synchronized(listeningClients) { listeningClients.isNotEmpty() }
+
+ override fun setDetailListening(show: Boolean) {
+ // do nothing like QSTileImpl
+ }
+
+ override fun destroy() {
+ adapterScope.cancel()
+ listeningScope.cancel()
+ qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+ }
+
+ override fun getState(): QSTile.State? =
+ qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+
+ override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
+ override fun getTileLabel(): CharSequence =
+ context.getString(qsTileViewModel.config.tileLabelRes)
+ override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec
+
+ private companion object {
+
+ const val DEBUG = false
+ const val TAG = "QSTileVMAdapter"
+
+ fun mapState(
+ context: Context,
+ viewModelState: QSTileState,
+ config: QSTileConfig
+ ): QSTile.State =
+ // we have to use QSTile.BooleanState to support different side icons
+ // which are bound to instanceof QSTile.BooleanState in QSTileView.
+ QSTile.BooleanState().apply {
+ spec = config.tileSpec.spec
+ label = viewModelState.label
+ // This value is synthetic and doesn't have any meaning
+ value = false
+
+ secondaryLabel = viewModelState.secondaryLabel
+ handlesLongClick =
+ viewModelState.supportedActions.contains(QSTileState.UserAction.LONG_CLICK)
+
+ iconSupplier = Supplier {
+ when (val stateIcon = viewModelState.icon()) {
+ is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
+ is Icon.Resource -> ResourceIcon.get(stateIcon.res)
+ }
+ }
+ state = viewModelState.activationState.legacyState
+
+ contentDescription = viewModelState.contentDescription
+ stateDescription = viewModelState.stateDescription
+
+ disabledByPolicy = viewModelState.enabledState == QSTileState.EnabledState.DISABLED
+ expandedAccessibilityClassName = viewModelState.expandedAccessibilityClassName
+
+ when (viewModelState.sideViewIcon) {
+ is QSTileState.SideViewIcon.Custom -> {
+ sideViewCustomDrawable =
+ when (viewModelState.sideViewIcon.icon) {
+ is Icon.Loaded -> viewModelState.sideViewIcon.icon.drawable
+ is Icon.Resource ->
+ context.getDrawable(viewModelState.sideViewIcon.icon.res)
+ }
+ }
+ is QSTileState.SideViewIcon.Chevron -> {
+ forceExpandIcon = true
+ }
+ is QSTileState.SideViewIcon.None -> {
+ forceExpandIcon = false
+ }
+ }
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter
+ }
+}
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 722d3661d0ae..a3499bd5c264 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
@@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
@@ -63,6 +64,7 @@ class SceneContainerStartable
constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val authenticationInteractor: AuthenticationInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val flags: SceneContainerFlags,
@@ -119,7 +121,7 @@ constructor(
/** Switches between scenes based on ever-changing application state. */
private fun automaticallySwitchScenes() {
applicationScope.launch {
- authenticationInteractor.isUnlocked
+ deviceEntryInteractor.isUnlocked
.mapNotNull { isUnlocked ->
val renderedScenes =
when (val transitionState = sceneInteractor.transitionState.value) {
@@ -130,7 +132,6 @@ constructor(
transitionState.toScene,
)
}
- val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
when {
@@ -141,7 +142,7 @@ constructor(
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
renderedScenes.contains(SceneKey.Lockscreen) ->
- if (isBypassEnabled) {
+ if (deviceEntryInteractor.isBypassEnabled()) {
SceneKey.Gone to
"device unlocked in Lockscreen scene with bypass"
} else {
@@ -191,7 +192,7 @@ constructor(
}
WakefulnessState.STARTING_TO_WAKE -> {
val authMethod = authenticationInteractor.getAuthenticationMethod()
- val isUnlocked = authenticationInteractor.isUnlocked.value
+ val isUnlocked = deviceEntryInteractor.isUnlocked.value
when {
authMethod == AuthenticationMethodModel.None -> {
switchToScene(
@@ -241,7 +242,7 @@ constructor(
/** Collects and reports signals into the falsing system. */
private fun collectFalsingSignals() {
applicationScope.launch {
- authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+ deviceEntryInteractor.isDeviceEntered.collect { isLockscreenDismissed ->
if (isLockscreenDismissed) {
falsingCollector.onSuccessfulUnlock()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 251cc168161c..ac8333ae84ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -129,6 +129,9 @@ constructor(
combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
.stateIn(scope, SharingStarted.Eagerly, 0f)
+ /** Whether either the shade or QS is fully expanded. */
+ val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
+
/** Whether either the shade or QS is expanding from a fully collapsed state. */
val isAnyExpanding: Flow<Boolean> =
anyExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 068d5a59ca2e..9c5a20189dd2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.shade.ui.viewmodel
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,15 +34,15 @@ class ShadeSceneViewModel
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
private val bouncerInteractor: BouncerInteractor,
val shadeHeaderViewModel: ShadeHeaderViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
combine(
- authenticationInteractor.isUnlocked,
- authenticationInteractor.canSwipeToDismiss,
+ deviceEntryInteractor.isUnlocked,
+ deviceEntryInteractor.canSwipeToEnter,
) { isUnlocked, canSwipeToDismiss ->
upDestinationSceneKey(
isUnlocked = isUnlocked,
@@ -54,8 +54,8 @@ constructor(
started = SharingStarted.WhileSubscribed(),
initialValue =
upDestinationSceneKey(
- isUnlocked = authenticationInteractor.isUnlocked.value,
- canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
+ isUnlocked = deviceEntryInteractor.isUnlocked.value,
+ canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 93bb4356a1d6..664103fa05d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -442,6 +442,7 @@ public class CommandQueue extends IStatusBar.Stub implements
* @see IStatusBar#requestAddTile
*/
default void requestAddTile(
+ int callingUid,
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@NonNull CharSequence label,
@@ -1314,6 +1315,7 @@ public class CommandQueue extends IStatusBar.Stub implements
@Override
public void requestAddTile(
+ int callingUid,
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@NonNull CharSequence label,
@@ -1326,6 +1328,7 @@ public class CommandQueue extends IStatusBar.Stub implements
args.arg3 = label;
args.arg4 = icon;
args.arg5 = callback;
+ args.arg6 = callingUid;
mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget();
}
@@ -1772,8 +1775,9 @@ public class CommandQueue extends IStatusBar.Stub implements
CharSequence label = (CharSequence) args.arg3;
Icon icon = (Icon) args.arg4;
IAddTileResultCallback callback = (IAddTileResultCallback) args.arg5;
+ int callingUid = (int) args.arg6;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).requestAddTile(
+ mCallbacks.get(i).requestAddTile(callingUid,
componentName, appName, label, icon, callback);
}
args.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index ea5ca276a8cf..fb6713962b73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,8 +49,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -103,7 +101,6 @@ public class NotificationLockscreenUserManagerImpl implements
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
- private final FeatureFlags mFeatureFlags;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
private LockPatternUtils mLockPatternUtils;
@@ -181,22 +178,7 @@ public class NotificationLockscreenUserManagerImpl implements
protected final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
- public void onUserChanged(int newUser, @NonNull Context userContext) {
- if (!mFeatureFlags.isEnabled(
- Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
- handleUserChange(newUser);
- }
- }
-
- @Override
public void onUserChanging(int newUser, @NonNull Context userContext) {
- if (mFeatureFlags.isEnabled(
- Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) {
- handleUserChange(newUser);
- }
- }
-
- private void handleUserChange(int newUser) {
mCurrentUserId = newUser;
updateCurrentProfilesCache();
@@ -239,8 +221,7 @@ public class NotificationLockscreenUserManagerImpl implements
KeyguardStateController keyguardStateController,
SecureSettings secureSettings,
DumpManager dumpManager,
- LockPatternUtils lockPatternUtils,
- FeatureFlags featureFlags) {
+ LockPatternUtils lockPatternUtils) {
mContext = context;
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
@@ -258,7 +239,6 @@ public class NotificationLockscreenUserManagerImpl implements
mDeviceProvisionedController = deviceProvisionedController;
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
- mFeatureFlags = featureFlags;
dumpManager.registerDumpable(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8d86d729d958..eedf35f1e9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.view.WindowInsets.Type.navigationBars;
+
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -66,7 +67,6 @@ import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
@@ -134,7 +134,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// dranw its first frame.
private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
- private static String TAG = "StatusBarKeyguardViewManager";
+ private static final String TAG = "StatusBarKeyguardViewManager";
private static final boolean DEBUG = false;
protected final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index efa092bb3f40..250fe53a2df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -22,11 +22,12 @@ import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -90,50 +91,56 @@ sealed interface WifiIcon : Diffable<WifiIcon> {
)}"
)
)
- is WifiNetworkModel.Active -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
- val contentDescription =
- ContentDescription.Loaded(
- if (model.isValidated) {
- (levelDesc)
- } else {
- "$levelDesc,${context.getString(NO_INTERNET)}"
- }
- )
- Visible(model.toIcon(showHotspotInfo), contentDescription)
- }
+ is WifiNetworkModel.Active -> model.toIcon(showHotspotInfo, context)
}
- @DrawableRes
- private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
- return if (!showHotspotInfo) {
- this.toBasicIcon()
+ private fun WifiNetworkModel.Active.toIcon(
+ showHotspotInfo: Boolean,
+ context: Context,
+ ): Visible {
+ return if (
+ !showHotspotInfo ||
+ this.hotspotDeviceType == WifiNetworkModel.HotspotDeviceType.NONE
+ ) {
+ this.toBasicIcon(context)
} else {
- when (this.hotspotDeviceType) {
- WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
- WifiNetworkModel.HotspotDeviceType.TABLET ->
- com.android.settingslib.R.drawable.ic_hotspot_tablet
- WifiNetworkModel.HotspotDeviceType.LAPTOP ->
- com.android.settingslib.R.drawable.ic_hotspot_laptop
- WifiNetworkModel.HotspotDeviceType.WATCH ->
- com.android.settingslib.R.drawable.ic_hotspot_watch
- WifiNetworkModel.HotspotDeviceType.AUTO ->
- com.android.settingslib.R.drawable.ic_hotspot_auto
- // Use phone as the default drawable
- WifiNetworkModel.HotspotDeviceType.PHONE,
- WifiNetworkModel.HotspotDeviceType.UNKNOWN,
- WifiNetworkModel.HotspotDeviceType.INVALID ->
- com.android.settingslib.R.drawable.ic_hotspot_phone
- }
+ val icon =
+ when (this.hotspotDeviceType) {
+ WifiNetworkModel.HotspotDeviceType.TABLET ->
+ com.android.settingslib.R.drawable.ic_hotspot_tablet
+ WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+ com.android.settingslib.R.drawable.ic_hotspot_laptop
+ WifiNetworkModel.HotspotDeviceType.WATCH ->
+ com.android.settingslib.R.drawable.ic_hotspot_watch
+ WifiNetworkModel.HotspotDeviceType.AUTO ->
+ com.android.settingslib.R.drawable.ic_hotspot_auto
+ // Use phone as the default drawable
+ WifiNetworkModel.HotspotDeviceType.PHONE,
+ WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+ WifiNetworkModel.HotspotDeviceType.INVALID ->
+ com.android.settingslib.R.drawable.ic_hotspot_phone
+ WifiNetworkModel.HotspotDeviceType.NONE ->
+ throw IllegalStateException("NONE checked earlier")
+ }
+ Visible(
+ icon,
+ ContentDescription.Loaded(context.getString(WIFI_OTHER_DEVICE_CONNECTION)),
+ )
}
}
- @DrawableRes
- private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+ private fun WifiNetworkModel.Active.toBasicIcon(context: Context): Visible {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
return if (this.isValidated) {
- WifiIcons.WIFI_FULL_ICONS[this.level]
+ Visible(
+ WifiIcons.WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc),
+ )
} else {
- WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+ Visible(
+ WifiIcons.WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded("$levelDesc,${context.getString(NO_INTERNET)}"),
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ae8128d20d3d..27f8121e6e66 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -67,7 +67,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
private final ToastLogger mToastLogger;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
- private ToastOutAnimatorListener mToastOutAnimatorListener;
+ @VisibleForTesting ToastOutAnimatorListener mToastOutAnimatorListener;
@VisibleForTesting SystemUIToast mToast;
private int mOrientation = ORIENTATION_PORTRAIT;
@@ -172,7 +172,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
if (mToast.getOutAnimation() != null) {
Animator animator = mToast.getOutAnimation();
mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
- runnable);
+ runnable, animator);
animator.addListener(mToastOutAnimatorListener);
animator.start();
} else {
@@ -211,14 +211,17 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
final ToastPresenter mPrevPresenter;
final ITransientNotificationCallback mPrevCallback;
@Nullable Runnable mShowNextToastRunnable;
+ @NonNull private final Animator mAnimator;
ToastOutAnimatorListener(
@NonNull ToastPresenter presenter,
@NonNull ITransientNotificationCallback callback,
- @Nullable Runnable runnable) {
+ @Nullable Runnable runnable,
+ @NonNull Animator animator) {
mPrevPresenter = presenter;
mPrevCallback = callback;
mShowNextToastRunnable = runnable;
+ mAnimator = animator;
}
void setShowNextToastRunnable(Runnable runnable) {
@@ -231,6 +234,8 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
if (mShowNextToastRunnable != null) {
mShowNextToastRunnable.run();
}
+ mAnimator.removeListener(this);
+ mShowNextToastRunnable = null;
mToastOutAnimatorListener = null;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f6649bdc9d3f..d54843d39d21 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,10 +36,8 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -47,6 +45,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -54,6 +53,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -157,7 +157,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private lateinit var authenticationInteractor: AuthenticationInteractor
+ private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
@@ -229,10 +229,10 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
- authenticationInteractor =
- sceneTestUtils.authenticationInteractor(
- repository = sceneTestUtils.authenticationRepository(),
- sceneInteractor = sceneInteractor
+ deviceEntryInteractor =
+ sceneTestUtils.deviceEntryInteractor(
+ authenticationInteractor = sceneTestUtils.authenticationInteractor(),
+ sceneInteractor = sceneInteractor,
)
underTest =
@@ -268,7 +268,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
keyguardTransitionInteractor,
primaryBouncerInteractor,
) {
- authenticationInteractor
+ deviceEntryInteractor
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index d3a2a73959dd..0283382d02be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -74,7 +74,6 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
getSecurityMode = getSecurityMode,
backgroundDispatcher = testUtils.testDispatcher,
userRepository = userRepository,
- keyguardRepository = testUtils.keyguardRepository,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 874053a83654..a102890db7b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -20,15 +20,12 @@ import android.app.admin.DevicePolicyManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -47,13 +44,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val repository: AuthenticationRepository = utils.authenticationRepository()
- private val sceneInteractor = utils.sceneInteractor()
- private val underTest =
- utils.authenticationInteractor(
- repository = repository,
- sceneInteractor = sceneInteractor,
- )
+ private val underTest = utils.authenticationInteractor()
@Test
fun authenticationMethod() =
@@ -79,10 +70,10 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
+ utils.authenticationRepository.setAuthenticationMethod(
+ DataLayerAuthenticationMethodModel.None
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
assertThat(underTest.getAuthenticationMethod())
@@ -95,10 +86,10 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(false)
- }
+ utils.authenticationRepository.setAuthenticationMethod(
+ DataLayerAuthenticationMethodModel.None
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(false)
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
assertThat(underTest.getAuthenticationMethod())
@@ -106,130 +97,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
}
@Test
- fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(false)
- // Toggle isUnlocked, twice.
- //
- // This is done because the underTest.isUnlocked flow doesn't receive values from
- // just changing the state above; the actual isUnlocked state needs to change to
- // cause the logic under test to "pick up" the current state again.
- //
- // It is done twice to make sure that we don't actually change the isUnlocked state
- // from what it originally was.
- setUnlocked(!utils.authenticationRepository.isUnlocked.value)
- runCurrent()
- setUnlocked(!utils.authenticationRepository.isUnlocked.value)
- runCurrent()
- }
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
- fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
-
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
- fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isTrue()
- }
-
- @Test
- fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isFalse()
- }
-
- @Test
- fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- setLockscreenEnabled(true)
- }
- switchToScene(SceneKey.Lockscreen)
- switchToScene(SceneKey.Gone)
-
- val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
- assertThat(canSwipeToDismiss).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_lockedAndSecured_true() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(false)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isTrue()
- }
-
- @Test
- fun isAuthenticationRequired_lockedAndNotSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(false)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_unlockedAndSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(true)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
- fun isAuthenticationRequired_unlockedAndNotSecured_false() =
- testScope.runTest {
- utils.authenticationRepository.apply {
- setUnlocked(true)
- runCurrent()
- setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
- }
-
- assertThat(underTest.isAuthenticationRequired()).isFalse()
- }
-
- @Test
fun authenticate_withCorrectPin_returnsTrue() =
testScope.runTest {
val isThrottled by collectLastValue(underTest.isThrottled)
@@ -366,7 +233,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -378,13 +244,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
)
.isEqualTo(AuthenticationResult.FAILED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -396,13 +262,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
)
.isEqualTo(AuthenticationResult.FAILED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(true)
@@ -414,13 +280,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
)
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isTrue()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
setAutoConfirmEnabled(false)
@@ -432,26 +298,27 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(
DataLayerAuthenticationMethodModel.Password
)
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.SKIPPED)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
assertThat(isUnlocked).isFalse()
}
@Test
fun throttling() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
val throttling by collectLastValue(underTest.throttling)
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(
@@ -462,7 +329,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(isUnlocked).isFalse()
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
@@ -605,8 +472,4 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertThat(hintedPinLength).isNull()
}
-
- private fun switchToScene(sceneKey: SceneKey) {
- sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 92c8a395a696..a9ba36a9fde4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -47,13 +47,16 @@ class BouncerInteractorTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
private val underTest =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -76,7 +79,7 @@ class BouncerInteractorTest : SysuiTestCase() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -111,7 +114,7 @@ class BouncerInteractorTest : SysuiTestCase() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
utils.authenticationRepository.setAutoConfirmEnabled(true)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -148,7 +151,7 @@ class BouncerInteractorTest : SysuiTestCase() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -180,7 +183,7 @@ class BouncerInteractorTest : SysuiTestCase() {
AuthenticationMethodModel.Password
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -215,7 +218,7 @@ class BouncerInteractorTest : SysuiTestCase() {
AuthenticationMethodModel.Pattern
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -268,7 +271,7 @@ class BouncerInteractorTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.showOrUnlockDevice()
@@ -281,8 +284,8 @@ class BouncerInteractorTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.authenticationRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
@@ -298,7 +301,7 @@ class BouncerInteractorTest : SysuiTestCase() {
AuthenticationMethodModel.Password
)
runCurrent()
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
val customMessage = "Hello there!"
underTest.showOrUnlockDevice(customMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f7dde02fdce..b5177e1587ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -37,17 +37,20 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index da2534d6fb14..b75355a82406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -44,12 +44,15 @@ class BouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
)
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = utils.sceneInteractor(),
)
@@ -223,6 +226,8 @@ class BouncerViewModelTest : SysuiTestCase() {
DataLayerAuthenticationMethodModel.Pattern
}
)
- setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
+ model !is DomainLayerAuthenticationMethodModel.None
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c1b33542267b..0926399a8617 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -44,13 +44,16 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ )
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -139,7 +142,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -207,7 +210,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index bf109d9b2b61..2e7c9aaa67e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -47,13 +47,16 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ )
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -378,7 +381,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2576204c247f..255bbe3ae231 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -47,12 +47,15 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
)
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -81,7 +84,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
@@ -103,7 +106,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
@@ -358,7 +361,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private fun TestScope.lockDeviceAndOpenPinBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 54142590b453..677108cab291 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -18,10 +18,13 @@ package com.android.systemui.controls.ui
import android.app.ActivityOptions
import android.app.PendingIntent
+import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -30,11 +33,13 @@ import com.android.systemui.util.mockito.capture
import com.android.wm.shell.taskview.TaskView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -43,31 +48,31 @@ import org.mockito.MockitoAnnotations
@TestableLooper.RunWithLooper
class DetailDialogTest : SysuiTestCase() {
- @Mock
- private lateinit var taskView: TaskView
- @Mock
- private lateinit var broadcastSender: BroadcastSender
- @Mock
- private lateinit var controlViewHolder: ControlViewHolder
- @Mock
- private lateinit var pendingIntent: PendingIntent
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
+ @Rule
+ @JvmField
+ val activityRule: ActivityScenarioRule<EmptyTestActivity> =
+ ActivityScenarioRule(EmptyTestActivity::class.java)
+
+ @Mock private lateinit var taskView: TaskView
+ @Mock private lateinit var broadcastSender: BroadcastSender
+ @Mock private lateinit var controlViewHolder: ControlViewHolder
+ @Mock private lateinit var pendingIntent: PendingIntent
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: DetailDialog
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ underTest = createDialog(pendingIntent)
}
@Test
fun testPendingIntentIsUnModified() {
- // GIVEN the dialog is created with a PendingIntent
- val dialog = createDialog(pendingIntent)
-
// WHEN the TaskView is initialized
- dialog.stateCallback.onInitialized()
+ underTest.stateCallback.onInitialized()
// THEN the PendingIntent used to call startActivity is unmodified by systemui
verify(taskView).startActivity(eq(pendingIntent), any(), any(), any())
@@ -75,11 +80,8 @@ class DetailDialogTest : SysuiTestCase() {
@Test
fun testActivityOptionsAllowBal() {
- // GIVEN the dialog is created with a PendingIntent
- val dialog = createDialog(pendingIntent)
-
// WHEN the TaskView is initialized
- dialog.stateCallback.onInitialized()
+ underTest.stateCallback.onInitialized()
val optionsCaptor = argumentCaptor<ActivityOptions>()
@@ -90,17 +92,41 @@ class DetailDialogTest : SysuiTestCase() {
.isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission)
.isTrue()
+ assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
+ }
+
+ @Test
+ fun testDismissRemovesTheTask() {
+ activityRule.scenario.onActivity {
+ underTest = createDialog(pendingIntent, it)
+ underTest.show()
+
+ underTest.dismiss()
+
+ verify(taskView).removeTask()
+ verify(taskView, never()).release()
+ }
+ }
+
+ @Test
+ fun testTaskRemovalReleasesTaskView() {
+ underTest.stateCallback.onTaskRemovalStarted(0)
+
+ verify(taskView).release()
}
- private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
+ private fun createDialog(
+ pendingIntent: PendingIntent,
+ context: Context = mContext,
+ ): DetailDialog {
return DetailDialog(
- mContext,
+ context,
broadcastSender,
taskView,
pendingIntent,
controlViewHolder,
keyguardStateController,
- activityStarter
+ activityStarter,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
new file mode 100644
index 000000000000..8e8cbe4a7cf2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -0,0 +1,127 @@
+package com.android.systemui.deviceentry.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ private val testUtils = SceneTestUtils(this)
+ private val testScope = testUtils.testScope
+ private val userRepository = FakeUserRepository()
+
+ private lateinit var underTest: DeviceEntryRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ userRepository.setUserInfos(USER_INFOS)
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+ underTest =
+ DeviceEntryRepositoryImpl(
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = testUtils.testDispatcher,
+ userRepository = userRepository,
+ lockPatternUtils = lockPatternUtils,
+ keyguardBypassController = keyguardBypassController,
+ keyguardStateController = keyguardStateController,
+ )
+ }
+
+ @Test
+ fun isUnlocked() =
+ testScope.runTest {
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ Mockito.verify(keyguardStateController, Mockito.atLeastOnce())
+ .addCallback(captor.capture())
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(true)
+ captor.value.onUnlockedChanged()
+ runCurrent()
+ assertThat(isUnlocked).isTrue()
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun isInsecureLockscreenEnabled() =
+ testScope.runTest {
+ whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false)
+ whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true)
+
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(underTest.isInsecureLockscreenEnabled()).isTrue()
+
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+ assertThat(underTest.isInsecureLockscreenEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_disabledInController() =
+ testScope.runTest {
+ whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
+ whenever(keyguardBypassController.bypassEnabled).thenAnswer { false }
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInController() =
+ testScope.runTest {
+ whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true }
+ whenever(keyguardBypassController.bypassEnabled).thenAnswer { true }
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(
+ UserInfo(
+ /* id= */ 100,
+ /* name= */ "First user",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ /* id= */ 101,
+ /* name= */ "Second user",
+ /* flags= */ 0,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
new file mode 100644
index 000000000000..55582e123969
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -0,0 +1,234 @@
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryInteractorTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val underTest =
+ utils.deviceEntryInteractor(
+ repository = repository,
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.apply {
+ setInsecureLockscreenEnabled(false)
+
+ // Toggle isUnlocked, twice.
+ //
+ // This is done because the underTest.isUnlocked flow doesn't receive values from
+ // just changing the state above; the actual isUnlocked state needs to change to
+ // cause the logic under test to "pick up" the current state again.
+ //
+ // It is done twice to make sure that we don't actually change the isUnlocked state
+ // from what it originally was.
+ setUnlocked(!isUnlocked.value)
+ runCurrent()
+ setUnlocked(!isUnlocked.value)
+ runCurrent()
+ }
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onLockscreenWithSwipe_isFalse() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Shade)
+
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+
+ assertThat(isDeviceEntered).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+ runCurrent()
+ switchToScene(SceneKey.Shade)
+
+ assertThat(isDeviceEntered).isTrue()
+ }
+
+ @Test
+ fun isDeviceEntered_onBouncer_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Bouncer)
+
+ val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+ assertThat(isDeviceEntered).isFalse()
+ }
+
+ @Test
+ fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isTrue()
+ }
+
+ @Test
+ fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isFalse()
+ }
+
+ @Test
+ fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ switchToScene(SceneKey.Lockscreen)
+ runCurrent()
+ switchToScene(SceneKey.Gone)
+
+ val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
+ assertThat(canSwipeToEnter).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndSecured_true() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+
+ assertThat(underTest.isAuthenticationRequired()).isTrue()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndNotSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndNotSecured_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInRepository_true() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setBypassEnabled(true)
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ @Test
+ fun isBypassEnabled_disabledInRepository_false() =
+ testScope.runTest {
+ utils.deviceEntryRepository.setBypassEnabled(false)
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ private fun switchToScene(sceneKey: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 2691860bc25b..f93051c41b61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -44,7 +44,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -81,7 +80,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
@@ -103,7 +101,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
screenLifecycle,
biometricUnlockController,
keyguardStateController,
- keyguardBypassController,
keyguardUpdateMonitor,
dozeTransitionListener,
dozeParameters,
@@ -213,23 +210,9 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isBypassEnabled_disabledInController() {
- whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
- whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
- assertThat(underTest.isBypassEnabled()).isFalse()
- }
-
- @Test
- fun isBypassEnabled_enabledInController() {
- whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
- whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
- assertThat(underTest.isBypassEnabled()).isTrue()
- }
-
- @Test
fun isAodAvailable() = runTest {
val flow = underTest.isAodAvailable
- var isAodAvailable = collectLastValue(flow)
+ val isAodAvailable = collectLastValue(flow)
runCurrent()
val callback =
@@ -273,29 +256,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isKeyguardUnlocked() =
- testScope.runTest {
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
-
- runCurrent()
- assertThat(isKeyguardUnlocked).isFalse()
-
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
-
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
- captor.value.onUnlockedChanged()
- runCurrent()
- assertThat(isKeyguardUnlocked).isTrue()
-
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- runCurrent()
- assertThat(isKeyguardUnlocked).isFalse()
- }
-
- @Test
fun isDozing() =
testScope.runTest {
underTest.setIsDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 90e217f3418c..82c7fa4c4d12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -15,8 +15,6 @@
*
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
@@ -27,13 +25,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -42,62 +38,53 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onCompletion
-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.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- private lateinit var commandQueue: FakeCommandQueue
- private lateinit var featureFlags: FakeFeatureFlags
- private lateinit var testScope: TestScope
-
- private lateinit var underTest: KeyguardInteractor
- private lateinit var repository: FakeKeyguardRepository
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var sceneInteractor: SceneInteractor
- private lateinit var transitionState: MutableStateFlow<ObservableTransitionState>
+
+ private val testUtils = SceneTestUtils(this)
+ private val testScope = testUtils.testScope
+ private val repository = testUtils.keyguardRepository
+ private val sceneInteractor = testUtils.sceneInteractor()
+ private val commandQueue = FakeCommandQueue()
+ private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) }
+ private val bouncerRepository = FakeKeyguardBouncerRepository()
+ private val configurationRepository = FakeConfigurationRepository()
+ private val shadeRepository = FakeShadeRepository()
+ private val transitionState: MutableStateFlow<ObservableTransitionState> =
+ MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
+
+ private val underTest =
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
+ sceneContainerFlags = testUtils.sceneContainerFlags,
+ deviceEntryRepository = testUtils.deviceEntryRepository,
+ bouncerRepository = bouncerRepository,
+ configurationRepository = configurationRepository,
+ shadeRepository = shadeRepository,
+ sceneInteractorProvider = { sceneInteractor },
+ )
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
- featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
- commandQueue = FakeCommandQueue()
- val sceneTestUtils = SceneTestUtils(this)
- testScope = sceneTestUtils.testScope
- repository = sceneTestUtils.keyguardRepository
- bouncerRepository = FakeKeyguardBouncerRepository()
- configurationRepository = FakeConfigurationRepository()
- shadeRepository = FakeShadeRepository()
- sceneInteractor = sceneTestUtils.sceneInteractor()
- transitionState = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
sceneInteractor.setTransitionState(transitionState)
- underTest =
- KeyguardInteractor(
- repository = repository,
- commandQueue = commandQueue,
- featureFlags = featureFlags,
- sceneContainerFlags = sceneTestUtils.sceneContainerFlags,
- bouncerRepository = bouncerRepository,
- configurationRepository = configurationRepository,
- shadeRepository = shadeRepository,
- sceneInteractorProvider = { sceneInteractor },
- )
}
@Test
fun onCameraLaunchDetected() =
testScope.runTest {
val flow = underTest.onCameraLaunchDetected
- var cameraLaunchSource = collectLastValue(flow)
+ val cameraLaunchSource = collectLastValue(flow)
runCurrent()
commandQueue.doForEachCallback {
@@ -175,7 +162,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
@Test
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
- var isVisible = collectLastValue(underTest.isKeyguardVisible)
+ val isVisible = collectLastValue(underTest.isKeyguardVisible)
repository.setKeyguardShowing(true)
repository.setKeyguardOccluded(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index f2636c543844..591653ee214f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1226,7 +1226,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// GIVEN the keyguard is showing locked
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- keyguardRepository.setKeyguardUnlocked(false)
runCurrent()
shadeRepository.setShadeModel(
ShadeModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 1681cfdce822..6e94691d42a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -37,10 +37,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
private val underTest = createLockscreenSceneViewModel()
@@ -49,8 +45,8 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.authenticationRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
+ utils.deviceEntryRepository.setUnlocked(true)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -61,7 +57,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -88,7 +84,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = utils.authenticationInteractor(),
+ sceneInteractor = utils.sceneInteractor(),
+ ),
communalInteractor = utils.communalInteractor(),
longPress =
KeyguardLongPressViewModel(
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 25faeef85e50..de57b603c7fe 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
@@ -81,6 +81,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock lateinit var logger: MediaViewLogger
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -121,7 +122,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
notifPanelEvents,
settings,
fakeHandler,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ logger,
)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index b595e8de3bad..79411f427f1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -66,6 +66,7 @@ import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
@@ -77,6 +78,8 @@ import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -102,8 +105,6 @@ public class QSTileHostTest extends SysuiTestCase {
private static final String SETTING = QSHost.TILES_SETTING;
@Mock
- private QSFactory mDefaultFactory;
- @Mock
private PluginManager mPluginManager;
@Mock
private TunerService mTunerService;
@@ -117,7 +118,6 @@ public class QSTileHostTest extends SysuiTestCase {
private CustomTile mCustomTile;
@Mock
private UserTracker mUserTracker;
- private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@Mock
@@ -127,6 +127,10 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private UserFileManager mUserFileManager;
+ private SecureSettings mSecureSettings;
+
+ private QSFactory mDefaultFactory;
+
private SparseArray<SharedPreferences> mSharedPreferencesByUser;
private FakeFeatureFlags mFeatureFlags;
@@ -144,6 +148,8 @@ public class QSTileHostTest extends SysuiTestCase {
mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
+ // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+ mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false);
mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -164,7 +170,8 @@ public class QSTileHostTest extends SysuiTestCase {
mSecureSettings = new FakeSettings();
saveSetting("");
- mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
+ setUpTileFactory();
+ mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
@@ -178,7 +185,6 @@ public class QSTileHostTest extends SysuiTestCase {
mMainExecutor.runAllReady();
}
}, mUserTracker.getUserId());
- setUpTileFactory();
}
private void saveSetting(String value) {
@@ -191,32 +197,29 @@ public class QSTileHostTest extends SysuiTestCase {
}
private void setUpTileFactory() {
- // Only create this kind of tiles
- when(mDefaultFactory.createTile(anyString())).thenAnswer(
- invocation -> {
- String spec = invocation.getArgument(0);
- if ("spec1".equals(spec)) {
- return new TestTile1(mQSTileHost);
- } else if ("spec2".equals(spec)) {
- return new TestTile2(mQSTileHost);
- } else if ("spec3".equals(spec)) {
- return new TestTile3(mQSTileHost);
- } else if ("na".equals(spec)) {
- return new NotAvailableTile(mQSTileHost);
- } else if (CUSTOM_TILE_SPEC.equals(spec)) {
- QSTile tile = mCustomTile;
- QSTile.State s = mock(QSTile.State.class);
- s.spec = spec;
- when(mCustomTile.getState()).thenReturn(s);
- return tile;
- } else if ("internet".equals(spec)
- || "wifi".equals(spec)
- || "cell".equals(spec)) {
- return new TestTile1(mQSTileHost);
- } else {
- return null;
- }
- });
+ mDefaultFactory = new FakeQSFactory(spec -> {
+ if ("spec1".equals(spec)) {
+ return new TestTile1(mQSTileHost);
+ } else if ("spec2".equals(spec)) {
+ return new TestTile2(mQSTileHost);
+ } else if ("spec3".equals(spec)) {
+ return new TestTile3(mQSTileHost);
+ } else if ("na".equals(spec)) {
+ return new NotAvailableTile(mQSTileHost);
+ } else if (CUSTOM_TILE_SPEC.equals(spec)) {
+ QSTile tile = mCustomTile;
+ QSTile.State s = mock(QSTile.State.class);
+ s.spec = spec;
+ when(mCustomTile.getState()).thenReturn(s);
+ return tile;
+ } else if ("internet".equals(spec)
+ || "wifi".equals(spec)
+ || "cell".equals(spec)) {
+ return new TestTile1(mQSTileHost);
+ } else {
+ return null;
+ }
+ });
when(mCustomTile.isAvailable()).thenReturn(true);
}
@@ -703,7 +706,7 @@ public class QSTileHostTest extends SysuiTestCase {
}
private class TestQSTileHost extends QSTileHost {
- TestQSTileHost(Context context,
+ TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles,
@@ -712,7 +715,7 @@ public class QSTileHostTest extends SysuiTestCase {
CustomTileStatePersister customTileStatePersister,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
- super(context, defaultFactory, mainExecutor, pluginManager,
+ super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, shadeController, qsLogger,
userTracker, secureSettings, customTileStatePersister,
tileLifecycleManagerFactory, userFileManager, featureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index bde30382ba05..d3cd26bcd3cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -124,6 +124,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
if (FACTORY_TILES.contains(spec)) {
FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
tile.setState(mState);
+ tile.setTileSpec(spec);
return tile;
} else {
return null;
@@ -284,7 +285,10 @@ public class TileQueryHelperTest extends SysuiTestCase {
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
QSTile t = mock(QSTile.class);
- when(mQSHost.createTile("hotspot")).thenReturn(t);
+ when(mQSHost.createTile("hotspot")).thenAnswer(invocation -> {
+ t.setTileSpec("hotspot");
+ return t;
+ });
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
"hotspot");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 126dd633108e..43cf1b5ecc40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -16,12 +16,15 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
+import android.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Handler
@@ -49,6 +52,7 @@ import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -67,6 +71,8 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.Arrays
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -74,10 +80,8 @@ import org.mockito.MockitoAnnotations
class CustomTileTest : SysuiTestCase() {
companion object {
- const val packageName = "test_package"
const val className = "test_class"
- val componentName = ComponentName(packageName, className)
- val TILE_SPEC = CustomTile.toSpec(componentName)
+ val UID = 12345
}
@Mock private lateinit var tileHost: QSHost
@@ -94,11 +98,36 @@ class CustomTileTest : SysuiTestCase() {
@Mock private lateinit var serviceInfo: ServiceInfo
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@Mock private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var ugm: IUriGrantsManager
private var displayTracker = FakeDisplayTracker(mContext)
private lateinit var customTile: CustomTile
private lateinit var testableLooper: TestableLooper
- private lateinit var customTileBuilder: CustomTile.Builder
+ private val packageName = context.packageName
+ private val componentName = ComponentName(packageName, className)
+ private val TILE_SPEC = CustomTile.toSpec(componentName)
+
+ private val customTileFactory = object : CustomTile.Factory {
+ override fun create(action: String, userContext: Context): CustomTile {
+ return CustomTile(
+ { tileHost },
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ action,
+ userContext,
+ customTileStatePersister,
+ tileServices,
+ displayTracker,
+ ugm,
+ )
+ }
+ }
@Before
fun setUp() {
@@ -116,24 +145,13 @@ class CustomTileTest : SysuiTestCase() {
`when`(packageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
.thenReturn(serviceInfo)
+ `when`(packageManager.getResourcesForApplication(any<ApplicationInfo>()))
+ .thenReturn(context.resources)
+
serviceInfo.applicationInfo = applicationInfo
- customTileBuilder = CustomTile.Builder(
- { tileHost },
- uiEventLogger,
- testableLooper.looper,
- Handler(testableLooper.looper),
- FalsingManagerFake(),
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- customTileStatePersister,
- tileServices,
- displayTracker
- )
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
}
@@ -146,7 +164,7 @@ class CustomTileTest : SysuiTestCase() {
`when`(userContext.packageManager).thenReturn(packageManager)
`when`(userContext.userId).thenReturn(10)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, userContext)
tile.initialize()
testableLooper.processAllMessages()
@@ -156,7 +174,7 @@ class CustomTileTest : SysuiTestCase() {
@Test
fun testToggleableTileHasBooleanState() {
`when`(tileServiceManager.isToggleableTile).thenReturn(true)
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
@@ -173,7 +191,7 @@ class CustomTileTest : SysuiTestCase() {
@Test
fun testValueUpdatedInBooleanTile() {
`when`(tileServiceManager.isToggleableTile).thenReturn(true)
- customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
customTile.initialize()
testableLooper.processAllMessages()
@@ -219,7 +237,7 @@ class CustomTileTest : SysuiTestCase() {
val t = Tile().apply {
state = Tile.STATE_INACTIVE
}
- customTile.updateTileState(t)
+ customTile.updateTileState(t, UID)
testableLooper.processAllMessages()
verify(customTileStatePersister, never()).persistState(any(), any())
@@ -243,7 +261,7 @@ class CustomTileTest : SysuiTestCase() {
`when`(tileServiceManager.isActiveTile).thenReturn(true)
`when`(customTileStatePersister
.readState(TileServiceKey(componentName, customTile.user))).thenReturn(t)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
testableLooper.processAllMessages()
@@ -281,11 +299,11 @@ class CustomTileTest : SysuiTestCase() {
}
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
testableLooper.processAllMessages()
- tile.updateTileState(t)
+ tile.updateTileState(t, UID)
testableLooper.processAllMessages()
@@ -297,13 +315,13 @@ class CustomTileTest : SysuiTestCase() {
fun testAvailableBeforeInitialization() {
`when`(packageManager.getApplicationInfo(anyString(), anyInt()))
.thenThrow(PackageManager.NameNotFoundException())
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
assertTrue(tile.isAvailable)
}
@Test
fun testNotAvailableAfterInitializationWithoutIcon() {
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
reset(tileHost)
tile.initialize()
testableLooper.processAllMessages()
@@ -315,7 +333,7 @@ class CustomTileTest : SysuiTestCase() {
fun testInvalidPendingIntentDoesNotStartActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(false)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
assertThrows(IllegalArgumentException::class.java) {
tile.qsTile.activityLaunchForClick = pi
@@ -333,7 +351,7 @@ class CustomTileTest : SysuiTestCase() {
fun testValidPendingIntentWithNoClickDoesNotStartActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
testableLooper.processAllMessages()
@@ -347,7 +365,7 @@ class CustomTileTest : SysuiTestCase() {
fun testValidPendingIntentStartsActivity() {
val pi = mock(PendingIntent::class.java)
`when`(pi.isActivity).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
tile.handleClick(mock(LaunchableFrameLayout::class.java))
@@ -363,7 +381,7 @@ class CustomTileTest : SysuiTestCase() {
fun testActiveTileListensOnceAfterCreated() {
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
tile.postStale()
testableLooper.processAllMessages()
@@ -376,7 +394,7 @@ class CustomTileTest : SysuiTestCase() {
fun testActiveTileDoesntListenAfterFirstTime() {
`when`(tileServiceManager.isActiveTile).thenReturn(true)
- val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext)
tile.initialize()
// Make sure we have an icon in the tile because we don't have a default icon
// This should not be overridden by the retrieved tile that has null icon.
@@ -424,19 +442,128 @@ class CustomTileTest : SysuiTestCase() {
// Set the tile to listening and apply the tile (unmodified)
customTile.handleSetListening(true)
testableLooper.processAllMessages()
- customTile.updateTileState(tile)
+ customTile.updateTileState(tile, UID)
customTile.refreshState()
testableLooper.processAllMessages()
assertThat(customTile.state.label).isEqualTo(label2)
}
- private fun copyTileUsingParcel(t: Tile): Tile {
- val parcel = Parcel.obtain()
- parcel.setDataPosition(0)
- t.writeToParcel(parcel, 0)
- parcel.setDataPosition(0)
+ @Test
+ fun uriIconLoadSuccess_correctIcon() {
+ val size = 100
+ val icon = mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(drawable)
+
+ serviceInfo.icon = R.drawable.android
+
+ customTile.handleSetListening(true)
+ testableLooper.processAllMessages()
+ customTile.handleSetListening(false)
+ testableLooper.processAllMessages()
+
+ val tile = copyTileUsingParcel(customTile.qsTile)
+ tile.icon = icon
+
+ customTile.updateTileState(tile, UID)
+
+ customTile.refreshState()
+ testableLooper.processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+ assertThat(
+ areDrawablesEqual(
+ customTile.state.iconSupplier.get().getDrawable(context),
+ drawable,
+ size
+ )
+ ).isTrue()
+ }
+
+ @Test
+ fun uriIconLoadFailsWithoutGrant_defaultIcon() {
+ val size = 100
+ val drawable = context.getDrawable(R.drawable.cloud)!!
+ val icon = mock(Icon::class.java)
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(null)
+
+ // Give it an icon to prevent issues
+ serviceInfo.icon = R.drawable.android
+
+ customTile.handleSetListening(true)
+ testableLooper.processAllMessages()
+ customTile.handleSetListening(false)
+ testableLooper.processAllMessages()
+
+ val tile = copyTileUsingParcel(customTile.qsTile)
+ tile.icon = icon
+
+ customTile.updateTileState(tile, UID)
+
+ customTile.refreshState()
+ testableLooper.processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName)
+
+ assertThat(
+ areDrawablesEqual(
+ customTile.state.iconSupplier.get().getDrawable(context),
+ context.getDrawable(R.drawable.android)!!,
+ size
+ )
+ ).isTrue()
+ }
+}
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+ val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+ val canvas1 = Canvas(bm1)
+ val canvas2 = Canvas(bm2)
+
+ drawable1.setBounds(0, 0, size, size)
+ drawable2.setBounds(0, 0, size, size)
+
+ drawable1.draw(canvas1)
+ drawable2.draw(canvas2)
- return Tile.CREATOR.createFromParcel(parcel)
+ return equalBitmaps(bm1, bm2).also {
+ bm1.recycle()
+ bm2.recycle()
}
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+ if (a.width != b.width || a.height != b.height) return false
+ val w = a.width
+ val h = a.height
+ val aPix = IntArray(w * h)
+ val bPix = IntArray(w * h)
+ a.getPixels(aPix, 0, w, 0, 0, w, h)
+ b.getPixels(bPix, 0, w, 0, 0, w, h)
+ return Arrays.equals(aPix, bPix)
+}
+
+private fun copyTileUsingParcel(t: Tile): Tile {
+ val parcel = Parcel.obtain()
+ parcel.setDataPosition(0)
+ t.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ return Tile.CREATOR.createFromParcel(parcel)
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 365e8a5187d4..78c2acf7209e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -16,6 +16,11 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -26,11 +31,21 @@ import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Arrays
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -40,12 +55,19 @@ class TileRequestDialogTest : SysuiTestCase() {
companion object {
private const val APP_NAME = "App name"
private const val LABEL = "Label"
+ private const val PACKAGE = "package"
+ private const val UID = 12345
+ private val DEFAULT_ICON = R.drawable.android
}
private lateinit var dialog: TileRequestDialog
+ @Mock
+ private lateinit var ugm: IUriGrantsManager
+
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
// Create in looper so we can make sure that the tile is fully updated
TestableLooper.get(this).runWithLooper {
dialog = TileRequestDialog(mContext)
@@ -62,9 +84,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectViews() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -77,9 +99,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectAppName() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
@@ -90,9 +112,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectLabel() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -105,9 +127,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasIcon() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -119,9 +141,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_nullIcon_hasIcon() {
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, null)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -134,9 +156,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasNoStateDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -150,9 +172,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileNotClickable() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -167,9 +189,9 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileHasCorrectContentDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
- dialog.setTileData(tileData)
+ dialog.setTileData(tileData, ugm)
dialog.show()
TestableLooper.get(this).processAllMessages()
@@ -179,4 +201,111 @@ class TileRequestDialogTest : SysuiTestCase() {
assertThat(tile.contentDescription).isEqualTo(LABEL)
}
+
+ @Test
+ fun uriIconLoadSuccess_correctIcon() {
+ val tintColor = Color.BLACK
+ val icon = Mockito.mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+ setTint(tintColor)
+ }
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(drawable)
+
+ val size = 100
+
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+ dialog.setTileData(tileData, ugm)
+ dialog.show()
+
+ TestableLooper.get(this).processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+ val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+ val tile = content.getChildAt(1) as QSTileView
+
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+ setTint(tintColor)
+ }
+
+ assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
+ }
+
+ @Test
+ fun uriIconLoadFail_defaultIcon() {
+ val tintColor = Color.BLACK
+ val icon = Mockito.mock(Icon::class.java)
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
+ setTint(tintColor)
+ }
+ whenever(icon.loadDrawable(any())).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(
+ any(),
+ eq(ugm),
+ anyInt(),
+ anyString())
+ ).thenReturn(null)
+
+ val size = 100
+
+ val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+
+ dialog.setTileData(tileData, ugm)
+ dialog.show()
+
+ TestableLooper.get(this).processAllMessages()
+
+ verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE))
+
+ val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+ val tile = content.getChildAt(1) as QSTileView
+
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
+ setTint(tintColor)
+ }
+
+ val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
+ setTint(tintColor)
+ }
+
+ assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
+ }
}
+
+private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean {
+ val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+ val canvas1 = Canvas(bm1)
+ val canvas2 = Canvas(bm2)
+
+ drawable1.setBounds(0, 0, size, size)
+ drawable2.setBounds(0, 0, size, size)
+
+ drawable1.draw(canvas1)
+ drawable2.draw(canvas2)
+
+ return equalBitmaps(bm1, bm2).also {
+ bm1.recycle()
+ bm2.recycle()
+ }
+}
+
+private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
+ if (a.width != b.width || a.height != b.height) return false
+ val w = a.width
+ val h = a.height
+ val aPix = IntArray(w * h)
+ val bPix = IntArray(w * h)
+ a.getPixels(aPix, 0, w, 0, 0, w, h)
+ b.getPixels(bPix, 0, w, 0, 0, w, h)
+ return Arrays.equals(aPix, bPix)
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index ccfb5cf8959a..3afa6adbc972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.external
+import android.app.IUriGrantsManager
import android.app.StatusBarManager
import android.content.ComponentName
import android.content.DialogInterface
@@ -57,6 +58,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
private const val TEST_APP_NAME = "App"
private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
}
@Mock
@@ -71,6 +73,8 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
private lateinit var logger: TileRequestDialogEventLogger
@Mock
private lateinit var icon: Icon
+ @Mock
+ private lateinit var ugm: IUriGrantsManager
private val instanceIdSequence = InstanceIdSequenceFake(1_000)
private lateinit var controller: TileServiceRequestController
@@ -88,7 +92,8 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
qsHost,
commandQueue,
commandRegistry,
- logger
+ logger,
+ ugm,
) {
tileRequestDialog
}
@@ -98,10 +103,24 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun requestTileAdd_dataIsPassedToDialog() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+ TileRequestDialog.TileData(
+ TEST_UID,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ TEST_COMPONENT.packageName,
+ ),
+ ugm,
)
}
@@ -110,7 +129,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
`when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
@@ -120,7 +146,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
fun tileAlreadyAdded_logged() {
`when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
verify(logger, never()).logDialogShown(anyString(), any())
@@ -129,19 +155,33 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun showAllUsers_set() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setShowForAllUsers(true)
}
@Test
fun cancelOnTouchOutside_set() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setCanceledOnTouchOutside(true)
}
@Test
fun dialogShown_logged() {
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
}
@@ -152,7 +192,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -165,7 +212,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val cancelListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -185,7 +232,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
@@ -199,7 +253,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
@@ -219,7 +273,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
@@ -233,7 +294,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val clickListenerCaptor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+ controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
@@ -257,10 +318,24 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback())
+ captor.value.requestAddTile(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
+ )
verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon)
+ TileRequestDialog.TileData(
+ TEST_UID,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ TEST_COMPONENT.packageName,
+ ),
+ ugm,
)
}
@@ -271,7 +346,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
val c = Callback()
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
+ captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
}
@@ -288,7 +363,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
throw RemoteException()
}
}
- captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ captor.value.requestAddTile(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
cancelListenerCaptor.value.onCancel(tileRequestDialog)
@@ -300,7 +382,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
dismissListenerCaptor.value.onDismiss(tileRequestDialog)
@@ -317,7 +406,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -338,7 +434,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
- controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
+ controller.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
+ )
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index dc1b9c4d0d14..a7505240caeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -46,6 +46,7 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.qs.toProto
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -91,6 +92,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
@Mock private lateinit var logger: QSPipelineLogger
+ @Mock private lateinit var newQSTileFactory: NewQSTileFactory
+
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -105,6 +108,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
+ // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+ featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
@@ -117,6 +122,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
userRepository = userRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
+ newQSTileFactory = { newQSTileFactory },
customTileAddedRepository = customTileAddedRepository,
tileLifecycleManagerFactory = tileLifecycleManagerFactory,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index e222542e5c53..067218a9c983 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -51,16 +51,17 @@ import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.util.leak.GarbageMonitor
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+import org.mockito.Mockito.`when` as whenever
private val specMap = mapOf(
"internet" to InternetTile::class.java,
@@ -98,7 +99,7 @@ private val specMap = mapOf(
class QSFactoryImplTest : SysuiTestCase() {
@Mock private lateinit var qsHost: QSHost
- @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+ @Mock private lateinit var customTileFactory: CustomTile.Factory
@Mock private lateinit var customTile: CustomTile
@Mock private lateinit var internetTile: InternetTile
@@ -139,7 +140,7 @@ class QSFactoryImplTest : SysuiTestCase() {
whenever(qsHost.context).thenReturn(mContext)
whenever(qsHost.userContext).thenReturn(mContext)
- whenever(customTileBuilder.build()).thenReturn(customTile)
+ whenever(customTileFactory.create(anyString(), any())).thenReturn(customTile)
val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>(
"internet" to Provider { internetTile },
@@ -174,7 +175,7 @@ class QSFactoryImplTest : SysuiTestCase() {
factory = QSFactoryImpl(
{ qsHost },
- { customTileBuilder },
+ { customTileFactory },
tileMap,
)
// When adding/removing tiles, fix also [specMap] and [tileMap]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
index 47b4244e0910..06b7a9fde873 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
@@ -1,12 +1,27 @@
-package com.android.systemui.qs.tiles.base
+/*
+ * 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.qs.tiles.base.actions
import android.content.Intent
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -17,7 +32,8 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
@Mock private lateinit var activityStarted: ActivityStarter
@@ -34,7 +50,7 @@ class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
fun testPassesIntentToStarter() {
val intent = Intent("test.ACTION")
- underTest.handle(QSTileUserAction.Click(context, null), intent)
+ underTest.handle(null, intent)
verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
new file mode 100644
index 000000000000..4f25d12aea49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.qs.tiles.base.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+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.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class DisabledByPolicyInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var restrictedLockProxy: RestrictedLockProxy
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var context: Context
+
+ @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ lateinit var underTest: DisabledByPolicyInteractor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ DisabledByPolicyInteractorImpl(
+ context,
+ activityStarter,
+ restrictedLockProxy,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun testEnabledWhenNoAdmin() =
+ testScope.runTest {
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(null)
+
+ assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+ .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+ }
+
+ @Test
+ fun testDisabledWhenAdminWithNoRestrictions() =
+ testScope.runTest {
+ val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin)
+ whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+ .thenReturn(false)
+
+ val result =
+ underTest.isDisabled(TEST_USER, TEST_RESTRICTION)
+ as DisabledByPolicyInteractor.PolicyResult.TileDisabled
+ assertThat(result.admin).isEqualTo(admin)
+ }
+
+ @Test
+ fun testEnabledWhenAdminWithRestrictions() =
+ testScope.runTest {
+ whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(ADMIN)
+ whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString()))
+ .thenReturn(true)
+
+ assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION))
+ .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+ }
+
+ @Test
+ fun testHandleDisabledByPolicy() {
+ val result =
+ underTest.handlePolicyResult(
+ DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
+ )
+
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ assertThat(result).isTrue()
+ verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+ assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
+ }
+
+ @Test
+ fun testHandleEnabled() {
+ val result =
+ underTest.handlePolicyResult(DisabledByPolicyInteractor.PolicyResult.TileEnabled)
+
+ assertThat(result).isFalse()
+ verify(activityStarter, never())
+ .postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
+ }
+
+ private companion object {
+ const val TEST_USER = 1
+ const val TEST_RESTRICTION = "test_restriction"
+
+ val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls")
+
+ val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 643866e3cade..9024c6c5576b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,12 +1,16 @@
package com.android.systemui.qs.tiles.viewmodel
-import android.graphics.drawable.Icon
-import android.testing.AndroidTestingRunner
+import android.graphics.drawable.ShapeDrawable
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import com.android.internal.logging.InstanceId
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
@@ -26,12 +30,13 @@ import org.junit.runner.RunWith
// TODO(b/299909368): Add more tests
@MediumTest
@RoboPilotTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
+ private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
private val testCoroutineDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testCoroutineDispatcher)
@@ -65,26 +70,27 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
scope: TestScope,
config: QSTileConfig = TEST_QS_TILE_CONFIG,
): QSTileViewModel =
- object :
- BaseQSTileViewModel<Any>(
- config,
- fakeQSTileUserActionInteractor,
- fakeQSTileDataInteractor,
- object : QSTileDataToStateMapper<Any> {
- override fun map(config: QSTileConfig, data: Any): QSTileState {
- return QSTileState(config.tileIcon, config.tileLabel)
- }
- },
- testCoroutineDispatcher,
- tileScope = scope.backgroundScope,
- ) {}
+ BaseQSTileViewModel(
+ config,
+ fakeQSTileUserActionInteractor,
+ fakeQSTileDataInteractor,
+ object : QSTileDataToStateMapper<Any> {
+ override fun map(config: QSTileConfig, data: Any): QSTileState =
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+ },
+ fakeDisabledByPolicyInteractor,
+ testCoroutineDispatcher,
+ scope.backgroundScope,
+ )
private companion object {
+
val TEST_QS_TILE_CONFIG =
QSTileConfig(
TileSpec.create("default"),
- Icon.createWithContentUri(""),
- "",
+ Icon.Loaded(ShapeDrawable(), null),
+ 0,
+ InstanceId.fakeInstanceId(0),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 8ae89304974d..f1c99d71b4e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -48,11 +48,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
- )
-
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private var mobileIconsViewModel: MobileIconsViewModel =
@@ -85,10 +80,17 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
broadcastDispatcher = fakeBroadcastDispatcher,
)
+ val authenticationInteractor = utils.authenticationInteractor()
+
underTest =
QuickSettingsSceneViewModel(
bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
@@ -101,7 +103,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -114,7 +116,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 85bd92bf3b12..5259013afc95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -83,7 +83,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
-
private val sceneContainerConfig = utils.fakeSceneContainerConfig()
private val sceneRepository =
utils.fakeSceneContainerRepository(
@@ -93,14 +92,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
utils.sceneInteractor(
repository = sceneRepository,
)
-
- private val authenticationRepository = utils.authenticationRepository()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
-
private val communalInteractor = utils.communalInteractor()
private val transitionState =
@@ -116,6 +113,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -128,7 +126,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val lockscreenSceneViewModel =
LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
longPress =
KeyguardLongPressViewModel(
@@ -178,12 +176,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
shadeSceneViewModel =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
bouncerInteractor = bouncerInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
)
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker)
@@ -191,6 +189,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
SceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
keyguardInteractor = keyguardInteractor,
flags = utils.sceneContainerFlags,
@@ -417,13 +416,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
// is not an observable that can trigger a new evaluation.
- authenticationRepository.setLockscreenEnabled(
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
authMethod !is DomainLayerAuthenticationMethodModel.None
)
- authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+ utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
if (!authMethod.isSecure) {
// When the auth method is not secure, the device is never considered locked.
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
}
runCurrent()
}
@@ -528,14 +527,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(authMethod.isSecure)
.isTrue()
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
}
/** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
private fun TestScope.unlockDevice() {
assertWithMessage("Cannot unlock a device that's already unlocked!")
- .that(authenticationInteractor.isUnlocked.value)
+ .that(deviceEntryInteractor.isUnlocked.value)
.isFalse()
emulateUserDrivenTransition(SceneKey.Bouncer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 16fdf8e40ed1..00a20ccc1c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -59,17 +59,13 @@ class SceneContainerStartableTest : SysuiTestCase() {
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
private val sceneContainerFlags = utils.sceneContainerFlags
- private val authenticationRepository = utils.authenticationRepository()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
- private val keyguardRepository = utils.keyguardRepository
- private val keyguardInteractor =
- utils.keyguardInteractor(
- repository = keyguardRepository,
- )
+ private val keyguardInteractor = utils.keyguardInteractor()
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
@@ -77,6 +73,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
SceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
keyguardInteractor = keyguardInteractor,
flags = sceneContainerFlags,
@@ -141,7 +138,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
- authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -157,7 +154,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -173,7 +170,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -189,7 +186,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -205,7 +202,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -251,7 +248,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -267,7 +264,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -283,7 +280,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -300,9 +297,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -389,11 +386,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
runCurrent()
verify(falsingCollector).setShowingAod(false)
- keyguardRepository.setIsDozing(true)
+ utils.keyguardRepository.setIsDozing(true)
runCurrent()
verify(falsingCollector).setShowingAod(true)
- keyguardRepository.setIsDozing(false)
+ utils.keyguardRepository.setIsDozing(false)
runCurrent()
verify(falsingCollector, times(2)).setShowingAod(false)
}
@@ -401,7 +398,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
testScope.runTest {
- keyguardRepository.setAodAvailable(false)
+ utils.keyguardRepository.setAodAvailable(false)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -414,31 +411,31 @@ class SceneContainerStartableTest : SysuiTestCase() {
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, times(1)).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
verify(falsingCollector, times(1)).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
verify(falsingCollector, times(2)).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(2)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
@@ -448,7 +445,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
testScope.runTest {
- keyguardRepository.setAodAvailable(true)
+ utils.keyguardRepository.setAodAvailable(true)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -461,31 +458,31 @@ class SceneContainerStartableTest : SysuiTestCase() {
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(ASLEEP)
+ utils.keyguardRepository.setWakefulnessModel(ASLEEP)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
verify(falsingCollector, never()).onScreenOff()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
@@ -521,8 +518,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
): MutableStateFlow<ObservableTransitionState> {
assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
sceneContainerFlags.enabled = true
- authenticationRepository.setUnlocked(isDeviceUnlocked)
- keyguardRepository.setBypassEnabled(isBypassEnabled)
+ utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
+ utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -534,8 +531,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
- authenticationRepository.setLockscreenEnabled(
+ utils.authenticationRepository.setAuthenticationMethod(
+ authenticationMethod.toDataLayer()
+ )
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(
authenticationMethod != AuthenticationMethodModel.None
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5c75d9cff10a..602bd5fded3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -47,9 +47,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = utils.authenticationRepository(),
+ private val authenticationInteractor = utils.authenticationInteractor()
+ private val deviceEntryInteractor =
+ utils.deviceEntryInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
@@ -88,9 +89,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
underTest =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- authenticationInteractor = authenticationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
bouncerInteractor =
utils.bouncerInteractor(
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
),
@@ -103,7 +105,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -113,7 +115,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -122,7 +124,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -134,7 +136,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.deviceEntryRepository.setInsecureLockscreenEnabled(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -147,7 +149,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
+ utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -160,7 +162,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
+ utils.deviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 39accfb5ffcc..0bc79a9f07d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -49,8 +49,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
@@ -112,7 +110,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private NotificationEntry mCurrentUserNotif;
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
- private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
@Before
public void setUp() {
@@ -295,21 +292,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testUserSwitchedCallsOnUserSwitching() {
- mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, true);
mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
mContext);
verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
}
@Test
- public void testUserSwitchedCallsOnUserSwitched() {
- mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, false);
- mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id,
- mContext);
- verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
- }
-
- @Test
public void testIsLockscreenPublicMode() {
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
@@ -368,8 +356,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mKeyguardStateController,
mSettings,
mock(DumpManager.class),
- mock(LockPatternUtils.class),
- mFakeFeatureFlags);
+ mock(LockPatternUtils.class));
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 15b9d61fc96b..c935dbb0ca1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -17,13 +17,14 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
@@ -181,6 +182,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -192,6 +195,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -203,6 +208,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -214,6 +221,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -225,6 +234,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -236,6 +247,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -247,6 +260,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
@@ -258,6 +273,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
assertThat(latest?.icon)
.isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index a520f6c109cc..49a2648a7cac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -136,6 +138,10 @@ class WifiViewModelTest : SysuiTestCase() {
// is used instead
assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+ assertThat(
+ (latest as WifiIcon.Visible).contentDescription.loadContentDescription(context)
+ )
+ .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 85359ca3aebe..bfc5bdb70c7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -342,6 +342,31 @@ public class ToastUITest extends SysuiTestCase {
}
@Test
+ public void testShowToast_afterShowToast_animationListenerCleanup() throws RemoteException {
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback, Display.DEFAULT_DISPLAY);
+ final SystemUIToast toast = mToastUI.mToast;
+
+ View view = verifyWmAddViewAndAttachToParent();
+ mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG,
+ null, Display.DEFAULT_DISPLAY);
+
+ if (toast.getOutAnimation() != null) {
+ assertThat(mToastUI.mToastOutAnimatorListener).isNotNull();
+ assertThat(toast.getOutAnimation().getListeners()
+ .contains(mToastUI.mToastOutAnimatorListener)).isTrue();
+ assertThat(toast.getOutAnimation().isRunning()).isTrue();
+ toast.getOutAnimation().cancel(); // end early if applicable
+ assertThat(toast.getOutAnimation().getListeners()).isNull();
+ }
+
+ verify(mWindowManager).removeViewImmediate(view);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
+ verify(mCallback).onToastHidden();
+ assertThat(mToastUI.mToastOutAnimatorListener).isNull();
+ }
+
+ @Test
public void testShowToast_logs() {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback, Display.DEFAULT_DISPLAY);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index f2e45281da93..4fc3e3f66e6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -24,20 +24,19 @@ import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAuthenticationRepository(
+ private val deviceEntryRepository: FakeDeviceEntryRepository,
private val currentTime: () -> Long,
) : AuthenticationRepository {
private val _isAutoConfirmEnabled = MutableStateFlow(false)
override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
- private val _isUnlocked = MutableStateFlow(false)
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override val hintedPinLength: Int = HINTING_PIN_LENGTH
private val _isPatternVisible = MutableStateFlow(true)
@@ -53,7 +52,6 @@ class FakeAuthenticationRepository(
override val minPatternLength: Int = 4
- private var isLockscreenEnabled = true
private var failedAttemptCount = 0
private var throttlingEndTimestamp = 0L
private var credentialOverride: List<Any>? = null
@@ -72,13 +70,9 @@ class FakeAuthenticationRepository(
credentialOverride = pin
}
- override suspend fun isLockscreenEnabled(): Boolean {
- return isLockscreenEnabled
- }
-
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
- _isUnlocked.value = isSuccessful
+ deviceEntryRepository.setUnlocked(isSuccessful)
}
override suspend fun getPinLength(): Int {
@@ -97,18 +91,10 @@ class FakeAuthenticationRepository(
_throttling.value = throttlingModel
}
- fun setUnlocked(isUnlocked: Boolean) {
- _isUnlocked.value = isUnlocked
- }
-
fun setAutoConfirmEnabled(isEnabled: Boolean) {
_isAutoConfirmEnabled.value = isEnabled
}
- fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
- this.isLockscreenEnabled = isLockscreenEnabled
- }
-
override suspend fun setThrottleDuration(durationMs: Int) {
throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
new file mode 100644
index 000000000000..5e60a09e006b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -0,0 +1,35 @@
+package com.android.systemui.deviceentry.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [DeviceEntryRepository] */
+class FakeDeviceEntryRepository : DeviceEntryRepository {
+
+ private var isInsecureLockscreenEnabled = true
+ private var isBypassEnabled = false
+
+ private val _isUnlocked = MutableStateFlow(false)
+ override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+ override fun isBypassEnabled(): Boolean {
+ return isBypassEnabled
+ }
+
+ override suspend fun isInsecureLockscreenEnabled(): Boolean {
+ return isInsecureLockscreenEnabled
+ }
+
+ fun setUnlocked(isUnlocked: Boolean) {
+ _isUnlocked.value = isUnlocked
+ }
+
+ fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) {
+ this.isInsecureLockscreenEnabled = isLockscreenEnabled
+ }
+
+ fun setBypassEnabled(isBypassEnabled: Boolean) {
+ this.isBypassEnabled = isBypassEnabled
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index a5f5d52ef56c..aa52609d6d47 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -63,9 +63,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
- private val _isKeyguardUnlocked = MutableStateFlow(false)
- override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
-
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -150,11 +147,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
return _isKeyguardShowing.value
}
- private var _isBypassEnabled = false
- override fun isBypassEnabled(): Boolean {
- return _isBypassEnabled
- }
-
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -252,14 +244,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_statusBarState.value = state
}
- fun setKeyguardUnlocked(isUnlocked: Boolean) {
- _isKeyguardUnlocked.value = isUnlocked
- }
-
- fun setBypassEnabled(isEnabled: Boolean) {
- _isBypassEnabled = isEnabled
- }
-
fun setScreenModel(screenModel: ScreenModel) {
_screenModel.value = screenModel
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 2e3bb2b545d8..1cae09b794b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -42,6 +43,7 @@ object KeyguardInteractorFactory {
sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
commandQueue: FakeCommandQueue = FakeCommandQueue(),
+ deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -52,6 +54,7 @@ object KeyguardInteractorFactory {
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -60,6 +63,7 @@ object KeyguardInteractorFactory {
commandQueue = commandQueue,
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = deviceEntryRepository,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -69,7 +73,7 @@ object KeyguardInteractorFactory {
}
/** Provide defaults, otherwise tests will throw an error */
- fun createFakeFeatureFlags(): FakeFeatureFlags {
+ private fun createFakeFeatureFlags(): FakeFeatureFlags {
return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
}
@@ -78,6 +82,7 @@ object KeyguardInteractorFactory {
val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
val sceneContainerFlags: SceneContainerFlags,
+ val deviceEntryRepository: FakeDeviceEntryRepository,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
index bf26e719433d..cbf4ae5e3014 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -21,6 +21,6 @@ import com.android.systemui.plugins.qs.QSTile
class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
override fun createTile(tileSpec: String): QSTile? {
- return tileCreator(tileSpec)
+ return tileCreator(tileSpec)?.also { it.tileSpec = tileSpec }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
new file mode 100644
index 000000000000..f62bf6014374
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
+
+ var handleResult: Boolean = false
+ var policyResult: DisabledByPolicyInteractor.PolicyResult =
+ DisabledByPolicyInteractor.PolicyResult.TileEnabled
+
+ override suspend fun isDisabled(
+ userId: Int,
+ userRestriction: String?
+ ): DisabledByPolicyInteractor.PolicyResult = policyResult
+
+ override fun handlePolicyResult(
+ policyResult: DisabledByPolicyInteractor.PolicyResult
+ ): Boolean = handleResult
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 13437c925367..1cb4ab76c9d5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import javax.annotation.CheckReturnValue
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 4e0266e25716..9c99cb52d5ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.qs.tiles.base.interactor
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 69c89e8f4af6..179206ff87b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -34,6 +34,9 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -73,16 +76,10 @@ class SceneTestUtils(
val testScope = TestScope(testDispatcher)
val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
- private val userRepository: UserRepository by lazy {
- FakeUserRepository().apply {
- val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
- setUserInfos(users)
- runBlocking { setSelectedUserInfo(users.first()) }
- }
- }
-
+ val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
val authenticationRepository: FakeAuthenticationRepository by lazy {
FakeAuthenticationRepository(
+ deviceEntryRepository = deviceEntryRepository,
currentTime = { testScope.currentTime },
)
}
@@ -103,6 +100,14 @@ class SceneTestUtils(
}
val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+ private val userRepository: UserRepository by lazy {
+ FakeUserRepository().apply {
+ val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
+ setUserInfos(users)
+ runBlocking { setSelectedUserInfo(users.first()) }
+ }
+ }
+
private val context = test.context
private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
@@ -145,31 +150,41 @@ class SceneTestUtils(
)
}
- fun authenticationRepository(): FakeAuthenticationRepository {
- return authenticationRepository
+ fun deviceEntryInteractor(
+ repository: DeviceEntryRepository = deviceEntryRepository,
+ authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+ ): DeviceEntryInteractor {
+ return DeviceEntryInteractor(
+ applicationScope = applicationScope(),
+ repository = repository,
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
}
fun authenticationInteractor(
- repository: AuthenticationRepository,
- sceneInteractor: SceneInteractor = sceneInteractor(),
+ repository: AuthenticationRepository = authenticationRepository,
): AuthenticationInteractor {
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
backgroundDispatcher = testDispatcher,
userRepository = userRepository,
- keyguardRepository = keyguardRepository,
- sceneInteractor = sceneInteractor,
+ deviceEntryRepository = deviceEntryRepository,
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
- fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+ fun keyguardInteractor(
+ repository: KeyguardRepository = keyguardRepository
+ ): KeyguardInteractor {
return KeyguardInteractor(
repository = repository,
commandQueue = FakeCommandQueue(),
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
+ deviceEntryRepository = FakeDeviceEntryRepository(),
bouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository = FakeConfigurationRepository(),
shadeRepository = FakeShadeRepository(),
@@ -185,6 +200,7 @@ class SceneTestUtils(
}
fun bouncerInteractor(
+ deviceEntryInteractor: DeviceEntryInteractor,
authenticationInteractor: AuthenticationInteractor,
sceneInteractor: SceneInteractor,
): BouncerInteractor {
@@ -192,6 +208,7 @@ class SceneTestUtils(
applicationScope = applicationScope(),
applicationContext = context,
repository = BouncerRepository(),
+ deviceEntryInteractor = deviceEntryInteractor,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
flags = sceneContainerFlags,
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 253ef43fa086..3583a7816ab2 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -39,7 +39,7 @@
"include-filter": "android.hardware.input.cts.tests"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
],
"file_patterns": ["Virtual[^/]*\\.java"]
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 6f243e18f6db..049b2fd032db 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "DisplayServiceTests",
"options": [
{"include-filter": "com.android.server.display"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 41053e908a00..68848a2ad426 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -16,35 +16,65 @@
package com.android.server.grammaticalinflection;
+import static android.app.Flags.systemTermsOfAddressEnabled;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import android.annotation.Nullable;
+import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
-import android.os.IBinder;
+import android.os.Environment;
import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.util.AtomicFile;
import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.Xml;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
/**
* The implementation of IGrammaticalInflectionManager.aidl.
*
* <p>This service is API entry point for storing app-specific grammatical inflection.
*/
public class GrammaticalInflectionService extends SystemService {
- private final String TAG = "GrammaticalInflection";
+ private static final String TAG = "GrammaticalInflection";
+ private static final String ATTR_NAME = "grammatical_gender";
+ private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml";
+ private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
+ private static final String GRAMMATICAL_INFLECTION_ENABLED =
+ "i18n.grammatical_Inflection.enabled";
+
private final GrammaticalInflectionBackupHelper mBackupHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private final Object mLock = new Object();
+ private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray();
+
private PackageManagerInternal mPackageManagerInternal;
- private static final String GRAMMATICAL_INFLECTION_ENABLED =
- "i18n.grammatical_Inflection.enabled";
+ private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
/**
* Initializes the system service.
@@ -62,22 +92,46 @@ public class GrammaticalInflectionService extends SystemService {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBackupHelper = new GrammaticalInflectionBackupHelper(
this, context.getPackageManager());
+ mBinderService = new GrammaticalInflectionBinderService();
}
@Override
public void onStart() {
- publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+ publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService);
LocalServices.addService(GrammaticalInflectionManagerInternal.class,
new GrammaticalInflectionManagerInternalImpl());
}
- private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+ private final class GrammaticalInflectionBinderService extends
+ IGrammaticalInflectionManager.Stub {
@Override
public void setRequestedApplicationGrammaticalGender(
String appPackageName, int userId, int gender) {
GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
appPackageName, userId, gender);
}
+
+ @Override
+ public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+ checkCallerIsSystem();
+ checkSystemTermsOfAddressIsEnabled();
+ GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
+ userId);
+ }
+
+ @Override
+ public int getSystemGrammaticalGender(int userId) {
+ checkSystemTermsOfAddressIsEnabled();
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new GrammaticalInflectionShellCommand(mBinderService))
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
};
private final class GrammaticalInflectionManagerInternalImpl
@@ -94,12 +148,6 @@ public class GrammaticalInflectionService extends SystemService {
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
}
-
- private void checkCallerIsSystem() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Caller is not system.");
- }
- }
}
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -137,4 +185,105 @@ public class GrammaticalInflectionService extends SystemService {
updater.setGrammaticalGender(gender).commit();
}
+
+ protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+ if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(grammaticalGender, stream));
+ atomicFile.finishWrite(stream);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
+ // gender.
+ public int getSystemGrammaticalGender(int userId) {
+ synchronized (mLock) {
+ final File file = getGrammaticalGenderFile(userId);
+ if (!file.exists()) {
+ Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+ try {
+ InputStream in = new FileInputStream(file);
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+ }
+ return mGrammaticalGenderCache.get(userId);
+ }
+ }
+
+ private File getGrammaticalGenderFile(int userId) {
+ final File dir = new File(Environment.getDataSystemCeDirectory(userId),
+ TAG_GRAMMATICAL_INFLECTION);
+ return new File(dir, USER_SETTINGS_FILE_NAME);
+ }
+
+ private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
+
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+ out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.attributeInt(null, ATTR_NAME, grammaticalGender);
+ out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+ out.endDocument();
+
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) {
+ return parser.getAttributeInt(null, ATTR_NAME);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ private void checkCallerIsSystem() {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
+ throw new SecurityException("Caller is not system and shell.");
+ }
+ }
+
+ private void checkSystemTermsOfAddressIsEnabled() {
+ if (!systemTermsOfAddressEnabled()) {
+ throw new RuntimeException("The flag must be enabled to allow calling the API.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
new file mode 100644
index 000000000000..d22372860ead
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.ActivityManager;
+import android.app.GrammaticalInflectionManager;
+import android.app.IGrammaticalInflectionManager;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link GrammaticalInflectionService}
+ */
+class GrammaticalInflectionShellCommand extends ShellCommand {
+
+ private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>();
+ static {
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ "Not specified (0)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)");
+ GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)");
+ }
+
+ private final IGrammaticalInflectionManager mBinderService;
+
+ GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+ mBinderService = grammaticalInflectionManager;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ switch (cmd) {
+ case "set-system-grammatical-gender":
+ return runSetSystemWideGrammaticalGender();
+ case "get-system-grammatical-gender":
+ return runGetSystemGrammaticalGender();
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(
+ " set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender "
+ + "<GRAMMATICAL_GENDER>]");
+ pw.println(" Set the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ pw.println(
+ " --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user "
+ + "preferred in system, not specified (0) is used when unspecified.");
+ pw.println(
+ " eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine"
+ + ".");
+ pw.println(
+ " get-system-grammatical-gender [--user <USER_ID>]");
+ pw.println(" Get the system grammatical gender for system.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ }
+
+ private int runSetSystemWideGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ case "-g":
+ case "--grammaticalGender": {
+ grammaticalGender = parseGrammaticalGender();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int runGetSystemGrammaticalGender() {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+ getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ return 0;
+ }
+
+ private int parseGrammaticalGender() {
+ String arg = getNextArg();
+ if (arg == null) {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ } else {
+ int grammaticalGender = Integer.parseInt(arg);
+ if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+ grammaticalGender)) {
+ return grammaticalGender;
+ } else {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
index 5f16ba9123b7..41d079ed9e75 100644
--- a/services/core/java/com/android/server/grammaticalinflection/OWNERS
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -2,3 +2,4 @@
allenwtsu@google.com
goldmanj@google.com
calvinpan@google.com
+zoeychen@google.com
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
index f868ea093500..17b98ce8e2d5 100644
--- a/services/core/java/com/android/server/lights/TEST_MAPPING
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -4,16 +4,14 @@
"name": "CtsHardwareTestCases",
"options": [
{"include-filter": "com.android.hardware.lights"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.lights"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING
index b881b44bbaa0..ddf3d76e97ae 100644
--- a/services/core/java/com/android/server/locksettings/TEST_MAPPING
+++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
@@ -20,7 +20,7 @@
"include-filter": "com.android.server.locksettings."
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
index f4b13a0e0bfa..904155226d7f 100644
--- a/services/core/java/com/android/server/logcat/TEST_MAPPING
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.logcat"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
}
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index a792498b8521..7aa9118e45ee 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "MediaProjectionTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index 7db2e8b1333f..468c4518602e 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsNotificationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -14,9 +11,6 @@
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
},
@@ -24,9 +18,6 @@
"name": "FrameworksUiServicesTests",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
@@ -34,9 +25,6 @@
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
- },
- {
- "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 390c45b6a524..0fd7a373c4aa 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -55,6 +55,7 @@
},
{
"name": "GtsContentTestCases",
+ "keywords": ["internal"],
"options": [
{
"include-filter": "com.google.android.content.gts"
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 819a82c24bfe..338b479f1ad1 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
@@ -49,7 +49,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.SplitPermissionTest"
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index 19086a184068..05a0e85d790f 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -3,16 +3,14 @@
{
"name": "CtsBatterySavingTestCases",
"options": [
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
"name": "FrameworksMockingServicesTests",
"options": [
{"include-filter": "com.android.server.power"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
},
@@ -20,7 +18,6 @@
"name": "PowerServiceTests",
"options": [
{"include-filter": "com.android.server.power"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 88eaafaee370..3fd832376d2b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2115,7 +2115,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+ bar.requestAddTile(callingUid, componentName, appName, label, icon, proxyCallback);
return;
} catch (RemoteException e) {
Slog.e(TAG, "requestAddTile", e);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d95bc77edcb..50bc825d8bea 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -255,6 +255,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
boolean mClearedForReorderActivityToFront;
/**
+ * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}.
+ */
+ boolean mIsSurfaceManagedBySystemOrganizer = false;
+
+ /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -449,13 +454,21 @@ class TaskFragment extends WindowContainer<WindowContainer> {
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@NonNull String processName) {
+ setTaskFragmentOrganizer(organizer, uid, processName,
+ false /* isSurfaceManagedBySystemOrganizer */);
+ }
+
+ void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+ @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) {
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
mTaskFragmentOrganizerUid = uid;
mTaskFragmentOrganizerProcessName = processName;
+ mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer;
}
void onTaskFragmentOrganizerRemoved() {
mTaskFragmentOrganizer = null;
+ mIsSurfaceManagedBySystemOrganizer = false;
}
/** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2396,6 +2409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
return;
}
+ if (mIsSurfaceManagedBySystemOrganizer) {
+ return;
+ }
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.isCollecting(this)) {
// TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index ea722b61be6f..04164c20a372 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -26,6 +26,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
@@ -50,12 +51,15 @@ import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -133,6 +137,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
new WeakHashMap<>();
/**
+ * Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true,
+ * the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the
+ * client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event.
+ */
+ private final boolean mIsSystemOrganizer;
+
+ /**
* {@link RemoteAnimationDefinition} for embedded activities transition animation that is
* organized by this organizer.
*/
@@ -147,10 +158,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
*/
private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
- TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
+ TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
+ boolean isSystemOrganizer) {
mOrganizer = organizer;
mOrganizerPid = pid;
mOrganizerUid = uid;
+ mIsSystemOrganizer = isSystemOrganizer;
try {
mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
} catch (RemoteException e) {
@@ -235,11 +248,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
tf.mTaskFragmentAppearedSent = true;
mLastSentTaskFragmentInfos.put(tf, info);
mTaskFragmentTaskIds.put(tf, taskId);
- return new TaskFragmentTransaction.Change(
+ final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_APPEARED)
.setTaskFragmentToken(tf.getFragmentToken())
.setTaskFragmentInfo(info)
.setTaskId(taskId);
+ if (mIsSystemOrganizer) {
+ change.setTaskFragmentSurfaceControl(tf.getSurfaceControl());
+ }
+ return change;
}
@NonNull
@@ -435,8 +452,25 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
: null;
}
+ @VisibleForTesting
+ void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ registerOrganizerInternal(organizer, false /* isSystemOrganizer */);
+ }
+
@Override
- public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ public void registerOrganizer(
+ @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+ registerOrganizerInternal(
+ organizer,
+ Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer);
+ }
+
+ @VisibleForTesting
+ void registerOrganizerInternal(
+ @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+ if (isSystemOrganizer) {
+ enforceTaskPermission("registerSystemOrganizer()");
+ }
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
@@ -448,7 +482,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
"Replacing existing organizer currently unsupported");
}
mTaskFragmentOrganizerState.put(organizer.asBinder(),
- new TaskFragmentOrganizerState(organizer, pid, uid));
+ new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer));
mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>());
}
}
@@ -711,6 +745,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
}
+ boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(token.asBinder());
+ return state != null && state.mIsSystemOrganizer;
+ }
+
@Nullable
private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
@NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6d7e2970e2b1..376cad734866 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -99,6 +99,7 @@ import android.window.IWindowOrganizerController;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -1993,8 +1994,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
creationParams.getFragmentToken(), true /* createdByOrganizer */);
// Set task fragment organizer immediately, since it might have to be notified about further
// actions.
- taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
- ownerActivity.getUid(), ownerActivity.info.processName);
+ TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
+ taskFragment.setTaskFragmentOrganizer(organizerToken,
+ ownerActivity.getUid(), ownerActivity.info.processName,
+ mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING
index eb9db702f7f9..7f7eb480a21d 100644
--- a/services/core/jni/TEST_MAPPING
+++ b/services/core/jni/TEST_MAPPING
@@ -7,7 +7,6 @@
"name": "CtsVibratorTestCases",
"options": [
{"exclude-annotation": "androidx.test.filters.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index fccd1ecf23c0..0d5534ba74df 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsDevicePolicyManagerTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index cedbfd2b2dde..de9f771a2a36 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "FrameworksInputMethodSystemServerTests",
"options": [
{"include-filter": "com.android.server.inputmethod"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
@@ -15,7 +14,6 @@
"name": "FrameworksImeTests",
"options": [
{"include-filter": "com.android.inputmethodservice"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING
index d73d83dedaae..a644ea690dcd 100644
--- a/services/tests/dreamservicetests/TEST_MAPPING
+++ b/services/tests/dreamservicetests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "DreamServiceTests",
"options": [
{"include-filter": "com.android.server.dreams"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index e1eb1e4fc0db..eee68a48fc63 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -4,7 +4,6 @@
"name": "PowerStatsTests",
"options": [
{"include-filter": "com.android.server.power.stats"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 4d1e4051032f..e9d8b2e709e7 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-filter": "com.android.server.recoverysystem."
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 5a482fc37998..f221b75564a3 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -484,6 +484,7 @@ public class StatusBarManagerServiceTest {
new Callback());
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -534,6 +535,7 @@ public class StatusBarManagerServiceTest {
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -555,6 +557,7 @@ public class StatusBarManagerServiceTest {
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -577,6 +580,7 @@ public class StatusBarManagerServiceTest {
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -599,6 +603,7 @@ public class StatusBarManagerServiceTest {
mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
verify(mMockStatusBar).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -623,6 +628,7 @@ public class StatusBarManagerServiceTest {
new Callback());
verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
@@ -638,6 +644,7 @@ public class StatusBarManagerServiceTest {
// Only called MAX_NUM_DENIALS times
verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+ anyInt(),
any(),
any(),
any(),
@@ -658,6 +665,7 @@ public class StatusBarManagerServiceTest {
new Callback());
verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(Binder.getCallingUid()),
eq(TEST_COMPONENT),
eq(APP_NAME),
eq(TILE_LABEL),
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index bfa279d5c3d5..2bf13857e537 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -212,7 +212,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.dispatchPendingEvents();
assertTaskFragmentParentInfoChangedTransaction(mTask);
- assertTaskFragmentAppearedTransaction();
+ assertTaskFragmentAppearedTransaction(false /* hasSurfaceControl */);
+ }
+
+ @Test
+ public void testOnTaskFragmentAppeared_systemOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+ // No-op when the TaskFragment is not attached.
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer, never()).onTransactionReady(any());
+
+ // Send callback when the TaskFragment is attached.
+ setupMockParent(mTaskFragment, mTask);
+
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ assertTaskFragmentParentInfoChangedTransaction(mTask);
+
+ // System organizer should receive the SurfaceControl
+ assertTaskFragmentAppearedTransaction(true /* hasSurfaceControl */);
}
@Test
@@ -1664,7 +1687,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
/** Asserts that there will be a transaction for TaskFragment appeared. */
- private void assertTaskFragmentAppearedTransaction() {
+ private void assertTaskFragmentAppearedTransaction(boolean hasSurfaceControl) {
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
@@ -1675,6 +1698,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
+ if (hasSurfaceControl) {
+ assertNotNull(change.getTaskFragmentSurfaceControl());
+ } else {
+ assertNull(change.getTaskFragmentSurfaceControl());
+ }
}
/** Asserts that there will be a transaction for TaskFragment info changed. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6a9bb6c85c70..5205bb0038c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -27,8 +27,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -124,6 +128,17 @@ public class TaskFragmentTest extends WindowTestsBase {
}
@Test
+ public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() {
+ clearInvocations(mTransaction);
+ mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true;
+
+ mTaskFragment.updateOrganizedTaskFragmentSurface();
+
+ verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat());
+ verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt());
+ }
+
+ @Test
public void testShouldStartChangeTransition_relativePositionChange() {
final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 36a8fc1c43a0..a5e0638fec95 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -18,6 +18,7 @@ package android.telephony;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,6 +53,7 @@ import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.internal.telephony.flags.Flags;
import com.android.telephony.Rlog;
import java.util.List;
@@ -9436,22 +9438,9 @@ public class CarrierConfigManager {
* </carrier_config>
* }</pre>
* <p>
- * If this carrier config is not present, the device overlay config
- * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
- * is present, the supported services associated with the PLMNs listed in the carrier config
- * will override that of the device overlay config. The supported satellite services will be
- * identified as follows:
- * <ul>
- * <li>For each PLMN that exists only in the carrier provided satellite services, use the
- * carrier provided services as the supported services.</li>
- * <li>For each PLMN that is present only in the device provided satellite services, use the
- * device provided services as the supported services.</li>
- * <li>For each PLMN that is present in both the carrier provided and device provided satellite
- * services, use the carrier provided services as the supported services.</li>
- * </ul>
- * <p>
* This config is empty by default.
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
"carrier_supported_satellite_services_per_provider_bundle";
@@ -9462,9 +9451,8 @@ public class CarrierConfigManager {
* satellite provider and the carrier before enabling this flag.
*
* The default value is false.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
"satellite_attach_supported_bool";
@@ -10430,8 +10418,12 @@ public class CarrierConfigManager {
sDefaults.putAll(Iwlan.getDefaults());
sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
- sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
- new int[] {4 /* BUSY */});
+ if (Flags.doNotOverridePreciseLabel()) {
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[]{});
+ } else {
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ new int[]{4 /* BUSY */});
+ }
sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000);
sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index a4527f12a199..8dc2de8178c4 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -85,29 +85,11 @@ public final class SatelliteManager {
@Nullable private final Context mContext;
/**
- * Create a new SatelliteManager object pinned to the given subscription ID.
- * This is needed only to handle carrier specific satellite features.
- * For eg: requestSatelliteAttachEnabledForCarrier and
- * requestIsSatelliteAttachEnabledForCarrier
- *
- * @return a SatelliteManager that uses the given subId for all satellite activities.
- * @throws IllegalArgumentException if the subscription is invalid.
- * @hide
- */
- public SatelliteManager createForSubscriptionId(int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- throw new IllegalArgumentException("Invalid subscription ID");
- }
- return new SatelliteManager(mContext, subId);
- }
-
- /**
* Create an instance of the SatelliteManager.
*
* @param context The context the SatelliteManager belongs to.
* @hide
*/
-
public SatelliteManager(@Nullable Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
@@ -846,8 +828,8 @@ public final class SatelliteManager {
/**
* Satellite communication restricted by geolocation. This can be
* triggered based upon geofence input provided by carrier to enable or disable satellite.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
/** @hide */
@@ -1643,26 +1625,28 @@ public final class SatelliteManager {
* {@code true}.</li>
* </ul>
*
+ * @param subId The subscription ID of the carrier.
* @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(executor);
Objects.requireNonNull(resultListener);
if (enableSatellite) {
- removeSatelliteAttachRestrictionForCarrier(
+ removeSatelliteAttachRestrictionForCarrier(subId,
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
} else {
- addSatelliteAttachRestrictionForCarrier(
+ addSatelliteAttachRestrictionForCarrier(subId,
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
}
}
@@ -1671,6 +1655,7 @@ public final class SatelliteManager {
* Request to get whether the carrier supported satellite plmn scan and attach by modem is
* enabled by user.
*
+ * @param subId The subscription ID of the carrier.
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
* If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -1681,16 +1666,17 @@ public final class SatelliteManager {
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void requestIsSatelliteAttachEnabledForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void requestIsSatelliteAttachEnabledForCarrier(int subId,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+ Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId);
executor.execute(() -> callback.onResult(
!restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
}
@@ -1699,19 +1685,25 @@ public final class SatelliteManager {
* Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
* by modem.
*
+ * @param subId The subscription ID of the carrier.
* @param reason Reason for disallowing satellite communication.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void addSatelliteAttachRestrictionForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void addSatelliteAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -1722,7 +1714,7 @@ public final class SatelliteManager {
() -> resultListener.accept(result)));
}
};
- telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1736,19 +1728,25 @@ public final class SatelliteManager {
* Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
* by modem.
*
+ * @param subId The subscription ID of the carrier.
* @param reason Reason for disallowing satellite communication.
* @param executor The executor on which the error code listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void removeSatelliteAttachRestrictionForCarrier(
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public void removeSatelliteAttachRestrictionForCarrier(int subId,
@SatelliteCommunicationRestrictionReason int reason,
@NonNull @CallbackExecutor Executor executor,
@SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -1759,7 +1757,7 @@ public final class SatelliteManager {
() -> resultListener.accept(result)));
}
};
- telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -1773,34 +1771,40 @@ public final class SatelliteManager {
* Get reasons for disallowing satellite attach, as requested by
* {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
*
+ * @param subId The subscription ID of the carrier.
* @return Set of reasons for disallowing satellite communication.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteCommunicationRestrictionReason
- public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
int[] receivedArray =
- telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+ telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId);
if (receivedArray.length == 0) {
- logd("received set is empty, create empty set");
+ logd("receivedArray is empty, create empty set");
return new HashSet<>();
} else {
return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
}
} else {
- throw new IllegalStateException("telephony service is null.");
+ throw new IllegalStateException("Telephony service is null.");
}
} catch (RemoteException ex) {
loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
- return null;
+ return new HashSet<>();
}
private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 02661de34fb2..0fcd0d622d36 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -392,7 +392,11 @@ oneway interface ISatellite {
*
* @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
* this information to determine the relevant carrier.
- * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+ * supported by user subscription.
+ * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+ * PLMNs that are not supported by the carrier and make sure not to
+ * attach to them.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -404,8 +408,8 @@ oneway interface ISatellite {
* SatelliteError:RADIO_NOT_AVAILABLE
* SatelliteError:REQUEST_NOT_SUPPORTED
*/
- void setSatellitePlmn(int simSlot, in List<String> plmnList,
- in IIntegerConsumer resultCallback);
+ void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
+ in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
/**
* Enable or disable satellite in the cellular modem associated with a carrier.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 6451daf2e752..a9c09c94d57c 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -214,12 +214,12 @@ public class SatelliteImplBase extends SatelliteService {
}
@Override
- public void setSatellitePlmn(int simSlot, List<String> plmnList,
- IIntegerConsumer errorCallback)
+ public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
+ List<String> devicePlmnList, IIntegerConsumer errorCallback)
throws RemoteException {
executeMethodAsync(
- () -> SatelliteImplBase.this
- .setSatellitePlmn(simSlot, plmnList, errorCallback),
+ () -> SatelliteImplBase.this.setSatellitePlmn(
+ simSlot, carrierPlmnList, devicePlmnList, errorCallback),
"setSatellitePlmn");
}
@@ -655,13 +655,15 @@ public class SatelliteImplBase extends SatelliteService {
* SIM profile. Acquisition of satellite based system is lower priority to terrestrial
* networks. UE shall make all attempts to acquire terrestrial service prior to camping on
* satellite LTE service.
- * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
- * and return an error otherwise.
*
* @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
* applied. The modem will use this information to determine the relevant carrier.
* @param errorCallback The callback to receive the error code result of the operation.
- * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+ * supported by user subscription.
+ * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+ * PLMNs that are not supported by the carrier and make sure not to
+ * attach to them.
*
* Valid error codes returned:
* SatelliteError:NONE
@@ -672,7 +674,8 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteError:RADIO_NOT_AVAILABLE
* SatelliteError:REQUEST_NOT_SUPPORTED
*/
- public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+ public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
+ @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
diff --git a/tests/utils/testutils/TEST_MAPPING b/tests/utils/testutils/TEST_MAPPING
index 6468d8cc99a6..52fd5a8779ad 100644
--- a/tests/utils/testutils/TEST_MAPPING
+++ b/tests/utils/testutils/TEST_MAPPING
@@ -7,9 +7,6 @@
"exclude-annotation": "androidx.test.filters.LargeTest"
},
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 95cbff9eeb12..d176b5e97b0b 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -69,7 +69,8 @@ public class HostTestUtils {
String methodDescriptor,
String callbackMethod
) {
- callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor);
+ callStaticMethodByName(callbackMethod, "method call hook", methodClass,
+ methodName, methodDescriptor);
}
/**
@@ -167,31 +168,38 @@ public class HostTestUtils {
logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
+ " calling hook " + callbackMethod);
- callStaticMethodByName(callbackMethod, loadedClass);
+ callStaticMethodByName(callbackMethod, "class load hook", loadedClass);
}
- private static void callStaticMethodByName(String classAndMethodName, Object... args) {
+ private static void callStaticMethodByName(String classAndMethodName,
+ String description, Object... args) {
// Forward the call to callbackMethod.
final int lastPeriod = classAndMethodName.lastIndexOf(".");
- final String className = classAndMethodName.substring(0, lastPeriod);
- final String methodName = classAndMethodName.substring(lastPeriod + 1);
- if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
+ if ((lastPeriod) < 0 || (lastPeriod == classAndMethodName.length() - 1)) {
throw new HostTestException(String.format(
- "Unable to find class load hook: malformed method name \"%s\"",
+ "Unable to find %s: malformed method name \"%s\"",
+ description,
classAndMethodName));
}
+ final String className = classAndMethodName.substring(0, lastPeriod);
+ final String methodName = classAndMethodName.substring(lastPeriod + 1);
+
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Class %s not found", className), e);
+ "Unable to find %s: Class %s not found",
+ description,
+ className), e);
}
if (!Modifier.isPublic(clazz.getModifiers())) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Class %s must be public", className));
+ "Unable to find %s: Class %s must be public",
+ description,
+ className));
}
Class<?>[] argTypes = new Class[args.length];
@@ -204,25 +212,23 @@ public class HostTestUtils {
method = clazz.getMethod(methodName, argTypes);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to find class load hook: class %s doesn't have method %s"
+ "Unable to find %s: class %s doesn't have method %s"
+ " (method must take exactly one parameter of type Class,"
+ " and public static)",
- className,
- methodName), e);
+ description, className, methodName), e);
}
if (!(Modifier.isPublic(method.getModifiers())
&& Modifier.isStatic(method.getModifiers()))) {
throw new HostTestException(String.format(
- "Unable to find class load hook: Method %s in class %s must be public static",
- methodName, className));
+ "Unable to find %s: Method %s in class %s must be public static",
+ description, methodName, className));
}
try {
method.invoke(null, args);
} catch (Exception e) {
throw new HostTestException(String.format(
- "Unable to invoke class load hook %s.%s",
- className,
- methodName), e);
+ "Unable to invoke %s %s.%s",
+ description, className, methodName), e);
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 7d7852ab162e..f75062b3a878 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -32,6 +32,10 @@ fun normalizeTextLine(s: String): String {
return uncommented.trim()
}
+/**
+ * Concatenate list [a] and [b] and return it. As an optimization, it returns an input
+ * [List] as-is if the other [List] is empty, so do not modify input [List]'s.
+ */
fun <T> addLists(a: List<T>, b: List<T>): List<T> {
if (a.isEmpty()) {
return b
@@ -42,6 +46,10 @@ fun <T> addLists(a: List<T>, b: List<T>): List<T> {
return a + b
}
+/**
+ * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a].
+ * (because the method may return [a] as-is, do not modify it after passing it.)
+ */
fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
if (b == null) {
return a