summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java34
-rw-r--r--core/java/android/content/Context.java7
-rw-r--r--core/java/android/flags/BooleanFlag.java52
-rw-r--r--core/java/android/flags/BooleanFlagBase.java82
-rw-r--r--core/java/android/flags/DynamicBooleanFlag.java50
-rw-r--r--core/java/android/flags/DynamicFlag.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt)19
-rw-r--r--core/java/android/flags/FeatureFlags.java379
-rw-r--r--core/java/android/flags/FeatureFlagsFake.java115
-rw-r--r--core/java/android/flags/Flag.java82
-rw-r--r--core/java/android/flags/FusedOffFlag.java49
-rw-r--r--core/java/android/flags/FusedOnFlag.java49
-rw-r--r--core/java/android/flags/IFeatureFlags.aidl89
-rw-r--r--core/java/android/flags/IFeatureFlagsCallback.aidl31
-rw-r--r--core/java/android/flags/OWNERS7
-rw-r--r--core/java/android/flags/SyncableFlag.aidl22
-rw-r--r--core/java/android/flags/SyncableFlag.java112
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java6
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java6
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java3
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java19
-rw-r--r--core/java/android/hardware/camera2/package.html28
-rw-r--r--core/java/android/hardware/camera2/params/ColorSpaceProfiles.java4
-rw-r--r--core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java14
-rw-r--r--core/java/android/hardware/camera2/params/SessionConfiguration.java7
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java30
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java19
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/view/WindowManager.java219
-rw-r--r--core/java/android/widget/RemoteViews.aidl1
-rw-r--r--core/java/android/widget/RemoteViews.java112
-rw-r--r--core/java/android/widget/RemoteViewsService.java31
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java11
-rw-r--r--core/java/com/android/internal/flags/CoreFlags.java93
-rw-r--r--core/java/com/android/internal/os/TimeoutRecord.java7
-rw-r--r--core/java/com/android/internal/widget/IRemoteViewsFactory.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java2
-rw-r--r--core/java/com/android/internal/widget/OWNERS3
-rw-r--r--core/proto/android/input/keyboard_configured.proto4
-rw-r--r--core/res/AndroidManifest.xml18
-rw-r--r--core/res/res/values-eu/strings.xml2
-rw-r--r--core/res/res/values-gl/strings.xml2
-rw-r--r--core/res/res/values-ko/strings.xml2
-rw-r--r--core/res/res/values-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/config_telephony.xml17
-rw-r--r--core/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/src/android/flags/FeatureFlagsTest.java155
-rw-r--r--core/tests/coretests/src/android/flags/IFeatureFlagsFake.java113
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java17
-rw-r--r--data/etc/platform.xml4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt57
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt108
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java14
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp32
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h22
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp3
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java52
-rw-r--r--packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java37
-rw-r--r--packages/SettingsLib/res/values-eu/arrays.xml6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java1
-rw-r--r--packages/Shell/res/values-gl/strings.xml4
-rw-r--r--packages/SystemUI/TEST_MAPPING30
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt42
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt46
-rw-r--r--packages/SystemUI/ktfmt_includes.txt4
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java3
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml1
-rw-r--r--packages/SystemUI/res/layout/media_smartspace_recommendations.xml136
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml7
-rw-r--r--packages/SystemUI/res/layout/zen_mode_condition.xml1
-rw-r--r--packages/SystemUI/res/values-af/strings.xml11
-rw-r--r--packages/SystemUI/res/values-am/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml11
-rw-r--r--packages/SystemUI/res/values-as/strings.xml11
-rw-r--r--packages/SystemUI/res/values-az/strings.xml11
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-be/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml11
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml11
-rw-r--r--packages/SystemUI/res/values-da/strings.xml11
-rw-r--r--packages/SystemUI/res/values-de/strings.xml11
-rw-r--r--packages/SystemUI/res/values-el/strings.xml7
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml11
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml7
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml11
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml11
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml7
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml11
-rw-r--r--packages/SystemUI/res/values-es/strings.xml9
-rw-r--r--packages/SystemUI/res/values-et/strings.xml11
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml13
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml11
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml11
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml11
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml11
-rw-r--r--packages/SystemUI/res/values-in/strings.xml11
-rw-r--r--packages/SystemUI/res/values-is/strings.xml11
-rw-r--r--packages/SystemUI/res/values-it/strings.xml15
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml11
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml15
-rw-r--r--packages/SystemUI/res/values-km/strings.xml11
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml11
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml11
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml11
-rw-r--r--packages/SystemUI/res/values-my/strings.xml11
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml11
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml11
-rw-r--r--packages/SystemUI/res/values-or/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml7
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml11
-rw-r--r--packages/SystemUI/res/values-si/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml11
-rw-r--r--packages/SystemUI/res/values-te/strings.xml11
-rw-r--r--packages/SystemUI/res/values-th/strings.xml11
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml11
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml11
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml11
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml11
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/res/xml/media_recommendation_collapsed.xml101
-rw-r--r--packages/SystemUI/res/xml/media_recommendation_expanded.xml123
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_collapsed.xml (renamed from packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml)0
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_expanded.xml (renamed from packages/SystemUI/res/xml/media_recommendations_view_expanded.xml)0
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt168
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt148
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java171
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt114
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt263
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt260
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt189
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt)464
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt500
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt142
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt425
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt1
-rw-r--r--services/Android.bp1
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java4
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java116
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java23
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java27
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java4
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java128
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricCameraManager.java (renamed from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java)13
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java68
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java37
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java62
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java26
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java24
-rw-r--r--services/core/java/com/android/server/display/BrightnessThrottler.java134
-rw-r--r--services/core/java/com/android/server/display/DisplayBrightnessState.java79
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java10
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java89
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java150
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java19
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java32
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java12
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java55
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java207
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java272
-rw-r--r--services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java152
-rw-r--r--services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java5
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java3
-rw-r--r--services/core/java/com/android/server/input/KeyboardMetricsCollector.java61
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java130
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java2
-rw-r--r--services/core/java/com/android/server/utils/FoldSettingWrapper.java48
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java7
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java48
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java5
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java19
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java47
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Transition.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/flags/Android.bp17
-rw-r--r--services/flags/OWNERS6
-rw-r--r--services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java280
-rw-r--r--services/flags/java/com/android/server/flags/FeatureFlagsBinder.java168
-rw-r--r--services/flags/java/com/android/server/flags/FeatureFlagsService.java113
-rw-r--r--services/flags/java/com/android/server/flags/FlagCache.java103
-rw-r--r--services/flags/java/com/android/server/flags/FlagOverrideStore.java122
-rw-r--r--services/flags/java/com/android/server/flags/FlagsShellCommand.java214
-rw-r--r--services/flags/java/com/android/server/flags/GlobalSettingsProxy.java68
-rw-r--r--services/flags/java/com/android/server/flags/SettingsProxy.java381
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--services/tests/displayservicetests/OWNERS (renamed from services/tests/displayservicetests/src/com/android/server/display/OWNERS)0
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java17
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java16
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java251
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java161
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java (renamed from services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java)0
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java39
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java24
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java21
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java112
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java293
-rw-r--r--services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java86
-rw-r--r--services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java111
-rw-r--r--services/tests/servicestests/src/com/android/server/flags/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt64
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java192
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java82
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java10
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java37
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java18
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java13
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt188
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt15
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml19
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml21
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml37
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml7
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java11
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java12
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java36
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java29
-rw-r--r--tests/Internal/src/android/service/wallpaper/OWNERS4
-rw-r--r--tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java13
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java42
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperTest.java40
441 files changed, 14765 insertions, 3445 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 13903acc0439..f429966e042a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -56,6 +56,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
import com.android.server.job.JobSchedulerInternal;
@@ -161,6 +162,9 @@ public final class JobStatus {
/** If the job is going to be passed an unmetered network. */
private boolean mHasAccessToUnmetered;
+ /** If the effective bucket has been downgraded once due to being buggy. */
+ private boolean mIsDowngradedDueToBuggyApp;
+
/**
* The additional set of dynamic constraints that must be met if this is an expedited job that
* had a long enough run while the device was Dozing or in battery saver.
@@ -1173,18 +1177,32 @@ public final class JobStatus {
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
+
+ final int bucketWithMediaExemption;
+ if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+ && mHasMediaBackupExemption) {
+ // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
+ // media backup jobs are important to the user, and the source package may not have
+ // been used directly in a while.
+ bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+ } else {
+ bucketWithMediaExemption = actualBucket;
+ }
+
// If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
// (potentially because it's used frequently by the user), limit its effective bucket
// so that it doesn't get to run as much as a normal ACTIVE app.
- final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
- if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
- && mHasMediaBackupExemption) {
- // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
- // to the user, and the
- // source package may not have been used directly in a while.
- return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
+ if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+ if (!mIsDowngradedDueToBuggyApp) {
+ // Safety check to avoid logging multiple times for the same job.
+ Counter.logIncrementWithUid(
+ "job_scheduler.value_job_quota_reduced_due_to_buggy_uid",
+ getTimeoutBlameUid());
+ mIsDowngradedDueToBuggyApp = true;
+ }
+ return WORKING_INDEX;
}
- return Math.max(highestBucket, actualBucket);
+ return bucketWithMediaExemption;
}
/** Returns the real standby bucket of the job. */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d82922484bc..2a6d84b1acc6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,6 +5298,13 @@ public abstract class Context {
public static final String APP_PREDICTION_SERVICE = "app_prediction";
/**
+ * Used for reading system-wide, overridable flags.
+ *
+ * @hide
+ */
+ public static final String FEATURE_FLAGS_SERVICE = "feature_flags";
+
+ /**
* Official published name of the search ui service.
*
* <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 000000000000..d4a35b25f623
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ BooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 000000000000..985dbe3f2f01
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag<Boolean> {
+
+ private final String mNamespace;
+ private final String mName;
+ private String mLabel;
+ private String mDescription;
+ private String mCategoryName;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ BooleanFlagBase(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ mLabel = name;
+ }
+
+ public abstract Boolean getDefault();
+
+ @Override
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+ mLabel = label;
+ mDescription = description;
+ mCategoryName = categoryName;
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public String getCategoryName() {
+ return mCategoryName;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+ }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 000000000000..271a8c5f4d15
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag<Boolean> {
+
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/core/java/android/flags/DynamicFlag.java
index d9c2cfa5ea7f..68819c58c064 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
+++ b/core/java/android/flags/DynamicFlag.java
@@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
+package android.flags;
/**
- * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
- * can roll this back into the old test.
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
*/
-@SmallTest
-class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
- override fun testNewLightBarLogic(): Boolean = true
+public interface DynamicFlag<T> extends Flag<T> {
+ @Override
+ default boolean isDynamic() {
+ return true;
+ }
}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 000000000000..8d3112c35d51
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+ private static final String TAG = "FeatureFlags";
+ private static FeatureFlags sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ private final Set<Flag<?>> mKnownFlags = new ArraySet<>();
+ private final Set<Flag<?>> mDirtyFlags = new ArraySet<>();
+
+ private IFeatureFlags mIFeatureFlags;
+ private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>();
+ private final Set<ChangeListener> mListeners = new HashSet<>();
+
+ /**
+ * Obtain a per-process instance of FeatureFlags.
+ * @return A singleton instance of {@link FeatureFlags}.
+ */
+ @NonNull
+ public static FeatureFlags getInstance() {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new FeatureFlags();
+ }
+ }
+
+ return sInstance;
+ }
+
+ /** See {@link FeatureFlagsFake}. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void setInstance(FeatureFlags instance) {
+ synchronized (sInstanceLock) {
+ sInstance = instance;
+ }
+ }
+
+ private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+ @Override
+ public void onFlagChange(SyncableFlag flag) {
+ for (Flag<?> f : mKnownFlags) {
+ if (flagEqualsSyncableFlag(f, flag)) {
+ if (f instanceof DynamicFlag<?>) {
+ if (f instanceof DynamicBooleanFlag) {
+ String value = flag.getValue();
+ if (value == null) { // Null means any existing overrides were erased.
+ value = ((DynamicBooleanFlag) f).getDefault().toString();
+ }
+ addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+ }
+ FeatureFlags.this.onFlagChange((DynamicFlag<?>) f);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ private FeatureFlags() {
+ this(null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public FeatureFlags(IFeatureFlags iFeatureFlags) {
+ mIFeatureFlags = iFeatureFlags;
+
+ if (mIFeatureFlags != null) {
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ // Shouldn't happen with things passed into tests.
+ Log.e(TAG, "Could not register callbacks!", e);
+ }
+ }
+ }
+
+ /**
+ * Construct a new {@link BooleanFlag}.
+ *
+ * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static BooleanFlag booleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Construct a new {@link FusedOffFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+ * flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOffFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link FusedOnFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOnFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link DynamicBooleanFlag}.
+ *
+ * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+ * the flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static DynamicBooleanFlag dynamicBooleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Add a listener to be alerted when a {@link DynamicFlag} changes.
+ *
+ * See also {@link #removeChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to add.
+ */
+ public void addChangeListener(@NonNull ChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was added earlier.
+ *
+ * See also {@link #addChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ public void removeChangeListener(@NonNull ChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected void onFlagChange(@NonNull DynamicFlag<?> flag) {
+ for (ChangeListener l : mListeners) {
+ l.onFlagChanged(flag);
+ }
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+ *
+ * The first time a flag is read, its value is cached for the lifetime of the process.
+ */
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns false.
+ */
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return false;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns true;
+ */
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return true;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Can return a different value for the flag each time it is called if an override comes in.
+ */
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ private boolean getBooleanInternal(Flag<Boolean> flag) {
+ sync();
+ Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace());
+ Boolean value = null;
+ if (ns != null) {
+ value = ns.get(flag.getName());
+ }
+ if (value == null) {
+ throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+ }
+
+ return value;
+ }
+
+ private <T extends Flag<?>> T addFlag(T flag) {
+ synchronized (FeatureFlags.class) {
+ mDirtyFlags.add(flag);
+ mKnownFlags.add(flag);
+ }
+ return flag;
+ }
+
+ /**
+ * Sync any known flags that have not yet been synced.
+ *
+ * This is called implicitly when any flag is read, and is not generally needed except in
+ * exceptional circumstances.
+ */
+ public void sync() {
+ synchronized (FeatureFlags.class) {
+ if (mDirtyFlags.isEmpty()) {
+ return;
+ }
+ syncInternal(mDirtyFlags);
+ mDirtyFlags.clear();
+ }
+ }
+
+ /**
+ * Called when new flags have been declared. Gives the implementation a chance to act on them.
+ *
+ * Guaranteed to be called from a synchronized, thread-safe context.
+ */
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ IFeatureFlags iFeatureFlags = bind();
+ List<SyncableFlag> syncableFlags = new ArrayList<>();
+ for (Flag<?> f : dirtyFlags) {
+ syncableFlags.add(flagToSyncableFlag(f));
+ }
+
+ List<SyncableFlag> serverFlags = List.of(); // Need to initialize the list with something.
+ try {
+ // New values come back from the service.
+ serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ for (Flag<?> f : dirtyFlags) {
+ boolean found = false;
+ for (SyncableFlag sf : serverFlags) {
+ if (flagEqualsSyncableFlag(f, sf)) {
+ if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+ addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (f instanceof BooleanFlag) {
+ addBooleanOverride(
+ f.getNamespace(),
+ f.getName(),
+ ((BooleanFlag) f).getDefault() ? "true" : "false");
+ }
+ }
+ }
+ }
+
+ private void addBooleanOverride(String namespace, String name, String override) {
+ Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace);
+ if (nsOverrides == null) {
+ nsOverrides = new HashMap<>();
+ mBooleanOverrides.put(namespace, nsOverrides);
+ }
+ nsOverrides.put(name, parseBoolean(override));
+ }
+
+ private SyncableFlag flagToSyncableFlag(Flag<?> f) {
+ return new SyncableFlag(
+ f.getNamespace(),
+ f.getName(),
+ f.getDefault().toString(),
+ f instanceof DynamicFlag<?>);
+ }
+
+ private IFeatureFlags bind() {
+ if (mIFeatureFlags == null) {
+ mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+ ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to listen for flag changes!");
+ }
+ }
+
+ return mIFeatureFlags;
+ }
+
+ static boolean parseBoolean(String value) {
+ // Check for a truish string.
+ boolean result = value.equalsIgnoreCase("true")
+ || value.equals("1")
+ || value.equalsIgnoreCase("t")
+ || value.equalsIgnoreCase("on");
+ if (!result) { // Expect a falsish string, else log an error.
+ if (!(value.equalsIgnoreCase("false")
+ || value.equals("0")
+ || value.equalsIgnoreCase("f")
+ || value.equalsIgnoreCase("off"))) {
+ Log.e(TAG,
+ "Tried parsing " + value + " as boolean but it doesn't look like one. "
+ + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+ }
+ }
+ return result;
+ }
+
+ private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) {
+ return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+ }
+
+
+ /**
+ * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+ *
+ * See {@link #addChangeListener(ChangeListener)}
+ */
+ public interface ChangeListener {
+ /**
+ * Called when a {@link DynamicFlag} changes.
+ *
+ * @param flag The flag that has changed.
+ */
+ void onFlagChanged(DynamicFlag<?> flag);
+ }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 000000000000..daedcdae0b5f
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.java
@@ -0,0 +1,115 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+ private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>();
+ private final Set<BooleanFlagBase> mReadFlags = new HashSet<>();
+
+ public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+ super(iFeatureFlags);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ }
+
+ /**
+ * Explicitly set a flag's value for reading in tests.
+ *
+ * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+ * flags having a fixed value at compile time, since unit tests should still test the state of
+ * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+ * build or vice versa.
+ *
+ * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+ * are held stable in the system, so changing a value after reading would not match
+ * real-implementation behavior.
+ *
+ * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+ * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+ *
+ * @param flag The BooleanFlag that you want to set a value for.
+ * @param value The value that the flag should return when accessed.
+ */
+ public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+ if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+ throw new RuntimeException(
+ "You can not set the value of a flag after it has been read. Tried to set "
+ + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+ }
+ mFlagValues.put(flag, value);
+ if (flag instanceof DynamicBooleanFlag) {
+ onFlagChange((DynamicFlag<?>) flag);
+ }
+ }
+
+ private boolean requireFlag(BooleanFlagBase flag) {
+ if (!mFlagValues.containsKey(flag)) {
+ throw new IllegalStateException(
+ "Tried to access " + flag + " in test but no overrided specified. You must "
+ + "call #setFlagValue for each flag read in a test.");
+ }
+ mReadFlags.add(flag);
+
+ return mFlagValues.get(flag);
+ }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 000000000000..b97a4c8a0fe7
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag<T> {
+ /** The namespace for a flag. Should combine uniquely with its name. */
+ @NonNull
+ String getNamespace();
+
+ /** The name of the flag. Should combine uniquely with its namespace. */
+ @NonNull
+ String getName();
+
+ /** The value of this flag if no override has been set. Null values are not supported. */
+ @NonNull
+ T getDefault();
+
+ /** Returns true if the value of this flag can change at runtime. */
+ default boolean isDynamic() {
+ return false;
+ }
+
+ /**
+ * Add human-readable details to the flag. Flag client's are not required to set this.
+ *
+ * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+ *
+ * @return Returns `this`, to make a fluent api.
+ */
+ Flag<T> defineMetaData(String label, String description, String categoryName);
+
+ /**
+ * A human-readable name for the flag. Defaults to {@link #getName()}
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ @NonNull
+ default String getLabel() {
+ return getName();
+ }
+
+ /**
+ * A human-readable description for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getDescription() {
+ return null;
+ }
+
+ /**
+ * A human-readable category name for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getCategoryName() {
+ return null;
+ }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 000000000000..6844b8faafef
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOffFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return false;
+ }
+
+ @Override
+ public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 000000000000..e9adba7595c1
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOnFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return true;
+ }
+
+ @Override
+ public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 000000000000..3efcec97fe6d
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * 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 android.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+ /**
+ * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+ * interest.
+ *
+ * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+ * includes what it thinks the default values of the flags are.
+ *
+ * The response will contain a list of matching SyncableFlags, whose values are set to what the
+ * value of the flags actually are. The client should update its internal state flag data to
+ * match.
+ *
+ * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+ * service will cache the passed-in value, and return it back out. If, however, a different
+ * client has synced that flag with the service previously, FeatureFlagsService will return the
+ * existing cached value, which may or may not be what the current client passed in. This allows
+ * FeatureFlagsService to keep clients in agreement with one another.
+ */
+ List<SyncableFlag> syncFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+ * changes.
+ */
+ void registerCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+ * {@link #registerCallback}.
+ */
+ void unregisterCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+ * cache them. See {@link #syncFlags}.
+ *
+ * You almost certainly don't want this method. This is intended for the Flag Flipper
+ * application that needs to query the state of system but doesn't want to affect it by
+ * doing so. All other clients should use {@link syncFlags}.
+ */
+ List<SyncableFlag> queryFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Change a flags value in the system.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void overrideFlag(in SyncableFlag flag);
+
+ /**
+ * Restore a flag to its default value.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void resetFlag(in SyncableFlag flag);
+} \ No newline at end of file
diff --git a/core/java/android/flags/IFeatureFlagsCallback.aidl b/core/java/android/flags/IFeatureFlagsCallback.aidl
new file mode 100644
index 000000000000..f708667ea4c4
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.SyncableFlag;
+
+/**
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
+ */
+oneway interface IFeatureFlagsCallback {
+ void onFlagChange(in SyncableFlag flag);
+} \ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 000000000000..fa125c4a159c
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/core/java/android/flags/SyncableFlag.aidl b/core/java/android/flags/SyncableFlag.aidl
new file mode 100644
index 000000000000..1526ec148967
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.flags;
+
+/**
+ * A parcelable data class for serializing {@link Flag} across a Binder.
+ */
+parcelable SyncableFlag; \ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 000000000000..449bcc3c49f5
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -0,0 +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 android.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+ private final String mNamespace;
+ private final String mName;
+ private final String mValue;
+ private final boolean mDynamic;
+ private final boolean mOverridden;
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic) {
+ this(namespace, name, value, dynamic, false);
+ }
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic,
+ boolean overridden
+ ) {
+ mNamespace = namespace;
+ mName = name;
+ mValue = value;
+ mDynamic = dynamic;
+ mOverridden = overridden;
+ }
+
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public String getValue() {
+ return mValue;
+ }
+
+ public boolean isDynamic() {
+ return mDynamic;
+ }
+
+ public boolean isOverridden() {
+ return mOverridden;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
+ public SyncableFlag createFromParcel(Parcel in) {
+ return new SyncableFlag(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readBoolean(),
+ in.readBoolean());
+ }
+
+ public SyncableFlag[] newArray(int size) {
+ return new SyncableFlag[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mNamespace);
+ dest.writeString(mName);
+ dest.writeString(mValue);
+ dest.writeBoolean(mDynamic);
+ dest.writeBoolean(mOverridden);
+ }
+
+ @Override
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getValue() + "]";
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 2e40f6096ccb..912e8df6bdc7 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,6 +144,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private Context mContext;
private IAuthService mService;
+ // LINT.IfChange
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
* @param context The {@link Context} that will be used to build the prompt.
@@ -417,6 +418,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
return this;
@@ -429,6 +431,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setReceiveSystemEvents(boolean set) {
mPromptInfo.setReceiveSystemEvents(set);
return this;
@@ -442,6 +445,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
return this;
@@ -454,10 +458,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIsForLegacyFingerprintManager(int sensorId) {
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1dc4b4b..e27507874167 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@ public class PromptInfo implements Parcelable {
dest.writeBoolean(mIsForLegacyFingerprintManager);
}
+ // LINT.IfChange
public boolean containsTestConfigurations() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@ public class PromptInfo implements Parcelable {
return true;
} else if (mAllowBackgroundAuthentication) {
return true;
+ } else if (mIsForLegacyFingerprintManager) {
+ return true;
+ } else if (mIgnoreEnrollmentState) {
+ return true;
}
return false;
}
@@ -144,6 +149,7 @@ public class PromptInfo implements Parcelable {
}
return false;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 99b297abe92a..0e45787c1340 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -283,7 +283,8 @@ public abstract class CameraDevice implements AutoCloseable {
* @see StreamConfigurationMap#getInputFormats
* @see StreamConfigurationMap#getInputSizes
* @see StreamConfigurationMap#getValidOutputFormatsForInput
- * @see StreamConfigurationMap#getOutputSizes
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
* @see android.media.ImageWriter
* @see android.media.ImageReader
* @deprecated Please use {@link
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a098362f16aa..c2fe0800812f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -130,14 +130,17 @@ public final class CameraManager {
/**
* Enable physical camera availability callbacks when the logical camera is unavailable
*
- * <p>Previously once a logical camera becomes unavailable, no {@link
- * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
- * the logical camera becomes available again. The results in the app opening the logical
- * camera not able to receive physical camera availability change.</p>
- *
- * <p>With this change, the {@link #onPhysicalCameraAvailable} and {@link
- * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
- * </p>
+ * <p>Previously once a logical camera becomes unavailable, no
+ * {@link AvailabilityCallback#onPhysicalCameraAvailable} or
+ * {@link AvailabilityCallback#onPhysicalCameraUnavailable} will
+ * be called until the logical camera becomes available again. The
+ * results in the app opening the logical camera not able to
+ * receive physical camera availability change.</p>
+ *
+ * <p>With this change, the {@link
+ * AvailabilityCallback#onPhysicalCameraAvailable} and {@link
+ * AvailabilityCallback#onPhysicalCameraUnavailable} can still be
+ * called while the logical camera is unavailable. </p>
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html
index 719c2f620234..3fd5d7c60832 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -62,12 +62,28 @@ RAW buffers for {@link android.hardware.camera2.DngCreator} can be
done with {@link android.media.ImageReader} with the {@link
android.graphics.ImageFormat#JPEG} and {@link
android.graphics.ImageFormat#RAW_SENSOR} formats. Application-driven
-processing of camera data in RenderScript, OpenGL ES, or directly in
-managed or native code is best done through {@link
-android.renderscript.Allocation} with a YUV {@link
-android.renderscript.Type}, {@link android.graphics.SurfaceTexture},
-and {@link android.media.ImageReader} with a {@link
-android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
+processing of camera data in OpenGL ES, or directly in managed or
+native code is best done through {@link
+android.graphics.SurfaceTexture}, or {@link android.media.ImageReader}
+with a {@link android.graphics.ImageFormat#YUV_420_888} format,
+respectively. </p>
+
+<p>By default, YUV-format buffers provided by the camera are using the
+JFIF YUV<->RGB transform matrix (equivalent to Rec.601 full-range
+encoding), and after conversion to RGB with this matrix, the resulting
+RGB data is in the sRGB colorspace. Captured JPEG images may contain
+an ICC profile to specify their color space information; if not, they
+should be assumed to be in the sRGB space as well. On some devices,
+the output colorspace can be changed via {@link
+android.hardware.camera2.params.SessionConfiguration#setColorSpace}.
+</p>
+<p>
+Note that although the YUV->RGB transform is the JFIF matrix (Rec.601
+full-range), due to legacy and compatibility reasons, the output is in
+the sRGB colorspace, which uses the Rec.709 color primaries. Image
+processing code can safely treat the output RGB as being in the sRGB
+colorspace.
+</p>
<p>The application then needs to construct a {@link
android.hardware.camera2.CaptureRequest}, which defines all the
diff --git a/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java b/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
index 2e3af80f9cc0..bb154a96cbe9 100644
--- a/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
+++ b/core/java/android/hardware/camera2/params/ColorSpaceProfiles.java
@@ -192,7 +192,7 @@ public final class ColorSpaceProfiles {
* @see OutputConfiguration#setDynamicRangeProfile
* @see SessionConfiguration#setColorSpace
* @see ColorSpace.Named
- * @see DynamicRangeProfiles.Profile
+ * @see DynamicRangeProfiles
*/
public @NonNull Set<Long> getSupportedDynamicRangeProfiles(@NonNull ColorSpace.Named colorSpace,
@ImageFormat.Format int imageFormat) {
@@ -230,7 +230,7 @@ public final class ColorSpaceProfiles {
* @see SessionConfiguration#setColorSpace
* @see OutputConfiguration#setDynamicRangeProfile
* @see ColorSpace.Named
- * @see DynamicRangeProfiles.Profile
+ * @see DynamicRangeProfiles
*/
public @NonNull Set<ColorSpace.Named> getSupportedColorSpacesForDynamicRange(
@ImageFormat.Format int imageFormat,
diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
index 80db38fc9d8f..d4ce0ebbc528 100644
--- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
@@ -45,7 +45,7 @@ import java.util.Set;
* Immutable class to store the recommended stream configurations to set up
* {@link android.view.Surface Surfaces} for creating a
* {@link android.hardware.camera2.CameraCaptureSession capture session} with
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession}.
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)}.
*
* <p>The recommended list does not replace or deprecate the exhaustive complete list found in
* {@link StreamConfigurationMap}. It is a suggestion about available power and performance
@@ -70,7 +70,7 @@ import java.util.Set;
* }</code></pre>
*
* @see CameraCharacteristics#getRecommendedStreamConfigurationMap
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public final class RecommendedStreamConfigurationMap {
@@ -282,7 +282,7 @@ public final class RecommendedStreamConfigurationMap {
/**
* Determine whether or not output surfaces with a particular user-defined format can be passed
- * {@link CameraDevice#createCaptureSession createCaptureSession}.
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration) createCaptureSession}.
*
* <p>
* For further information refer to {@link StreamConfigurationMap#isOutputSupportedFor}.
@@ -292,7 +292,7 @@ public final class RecommendedStreamConfigurationMap {
* @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
* @return
* {@code true} if using a {@code surface} with this {@code format} will be
- * supported with {@link CameraDevice#createCaptureSession}
+ * supported with {@link CameraDevice#createCaptureSession(SessionConfiguration)}
*
* @throws IllegalArgumentException
* if the image format was not a defined named constant
@@ -508,8 +508,10 @@ public final class RecommendedStreamConfigurationMap {
}
/**
- * Determine whether or not the {@code surface} in its current state is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the {@code surface} in its current
+ * state is suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* <p>For more information refer to {@link StreamConfigurationMap#isOutputSupportedFor}.
* </p>
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 385f10719509..8f611a831204 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -55,7 +55,7 @@ public final class SessionConfiguration implements Parcelable {
* at regular non high speed FPS ranges and optionally {@link InputConfiguration} for
* reprocessable sessions.
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see CameraDevice#createReprocessableCaptureSession
*/
public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL;
@@ -110,10 +110,7 @@ public final class SessionConfiguration implements Parcelable {
*
* @see #SESSION_REGULAR
* @see #SESSION_HIGH_SPEED
- * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
- * @see CameraDevice#createCaptureSessionByOutputConfigurations
- * @see CameraDevice#createReprocessableCaptureSession
- * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public SessionConfiguration(@SessionMode int sessionType,
@NonNull List<OutputConfiguration> outputs,
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index aabe149d16f5..ef0db7f8a41c 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -41,7 +41,7 @@ import java.util.Set;
* {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP configurations} to set up
* {@link android.view.Surface Surfaces} for creating a
* {@link android.hardware.camera2.CameraCaptureSession capture session} with
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession}.
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)}.
* <!-- TODO: link to input stream configuration -->
*
* <p>This is the authoritative list for all <!-- input/ -->output formats (and sizes respectively
@@ -62,7 +62,7 @@ import java.util.Set;
* }</code></pre>
*
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public final class StreamConfigurationMap {
@@ -456,7 +456,7 @@ public final class StreamConfigurationMap {
/**
* Determine whether or not output surfaces with a particular user-defined format can be passed
- * {@link CameraDevice#createCaptureSession createCaptureSession}.
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration) createCaptureSession}.
*
* <p>This method determines that the output {@code format} is supported by the camera device;
* each output {@code surface} target may or may not itself support that {@code format}.
@@ -468,7 +468,7 @@ public final class StreamConfigurationMap {
* @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
* @return
* {@code true} iff using a {@code surface} with this {@code format} will be
- * supported with {@link CameraDevice#createCaptureSession}
+ * supported with {@link CameraDevice#createCaptureSession(SessionConfiguration)}
*
* @throws IllegalArgumentException
* if the image format was not a defined named constant
@@ -476,7 +476,7 @@ public final class StreamConfigurationMap {
*
* @see ImageFormat
* @see PixelFormat
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
*/
public boolean isOutputSupportedFor(int format) {
checkArgumentFormat(format);
@@ -521,7 +521,7 @@ public final class StreamConfigurationMap {
*
* <p>Generally speaking this means that creating a {@link Surface} from that class <i>may</i>
* provide a producer endpoint that is suitable to be used with
- * {@link CameraDevice#createCaptureSession}.</p>
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.</p>
*
* <p>Since not all of the above classes support output of all format and size combinations,
* the particular combination should be queried with {@link #isOutputSupportedFor(Surface)}.</p>
@@ -531,7 +531,7 @@ public final class StreamConfigurationMap {
*
* @throws NullPointerException if {@code klass} was {@code null}
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Surface)
*/
public static <T> boolean isOutputSupportedFor(Class<T> klass) {
@@ -555,8 +555,10 @@ public final class StreamConfigurationMap {
}
/**
- * Determine whether or not the {@code surface} in its current state is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the {@code surface} in its current
+ * state is suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* <p>Not all surfaces are usable with the {@link CameraDevice}, and not all configurations
* of that {@code surface} are compatible. Some classes that provide the {@code surface} are
@@ -588,7 +590,7 @@ public final class StreamConfigurationMap {
* @throws NullPointerException if {@code surface} was {@code null}
* @throws IllegalArgumentException if the Surface endpoint is no longer valid
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Class)
*/
public boolean isOutputSupportedFor(Surface surface) {
@@ -623,14 +625,16 @@ public final class StreamConfigurationMap {
}
/**
- * Determine whether or not the particular stream configuration is suitable to be included
- * in a {@link CameraDevice#createCaptureSession capture session} as an output.
+ * Determine whether or not the particular stream configuration is
+ * suitable to be included in a {@link
+ * CameraDevice#createCaptureSession(SessionConfiguration) capture
+ * session} as an output.
*
* @param size stream configuration size
* @param format stream configuration format
* @return {@code true} if this is supported, {@code false} otherwise
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
* @see #isOutputSupportedFor(Class)
* @hide
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 753349c19e10..88c7250bdc15 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4709,6 +4709,15 @@ public final class Settings {
public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
/**
+ * Control whether to stay awake on fold
+ *
+ * If this isn't set, the system falls back to a device specific default.
+ * @hide
+ */
+ @Readable
+ public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index dbc1be141571..d9ac4850e924 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -868,6 +868,11 @@ public abstract class WallpaperService extends Service {
* This will trigger a {@link #onComputeColors()} call.
*/
public void notifyColorsChanged() {
+ if (mDestroyed) {
+ Log.i(TAG, "Ignoring notifyColorsChanged(), Engine has already been destroyed.");
+ return;
+ }
+
final long now = mClockFunction.get();
if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) {
Log.w(TAG, "This call has been deferred. You should only call "
@@ -2226,7 +2231,11 @@ public abstract class WallpaperService extends Service {
}
}
- void detach() {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public void detach() {
if (mDestroyed) {
return;
}
@@ -2442,6 +2451,14 @@ public abstract class WallpaperService extends Service {
}
public void reportShown() {
+ if (mEngine == null) {
+ Log.i(TAG, "Can't report null engine as shown.");
+ return;
+ }
+ if (mEngine.mDestroyed) {
+ Log.i(TAG, "Engine was destroyed before we could draw.");
+ return;
+ }
if (!mShownReported) {
mShownReported = true;
Trace.beginSection("WPMS.mConnection.engineShown");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f1cde3b4bb7e..2499be999f8d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21821,6 +21821,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mCurrentAnimation = null;
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
hideTooltip();
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d702367965a1..d5f2aa3b3631 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -880,22 +880,23 @@ public interface WindowManager extends ViewManager {
int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600;
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app can be opted-in or opted-out
- * from the compatibility treatment that avoids {@link
- * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
- * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
- * orientation of the device.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-in or opted-out from the
+ * compatibility treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
* using their discretion to improve display compatibility.
*
- * <p>With this property set to {@code true}, the system could ignore {@link
- * android.app.Activity#setRequestedOrientation} call from an app if one of the following
- * conditions are true:
+ * <p>With this property set to {@code true}, the system could ignore
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()} call
+ * from an app if one of the following conditions are true:
* <ul>
- * <li>Activity is relaunching due to the previous {@link
- * android.app.Activity#setRequestedOrientation} call.
+ * <li>Activity is relaunching due to the previous
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}
+ * call.
* <li>Camera compatibility force rotation treatment is active for the package.
* </ul>
*
@@ -919,14 +920,16 @@ public interface WindowManager extends ViewManager {
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* for an app to inform the system that the app can be opted-out from the compatibility
- * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
- * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
- * by the landscape natural orientation of the device.
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
- * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation}
- * call from an app if both of the following conditions are true:
+ * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} call from an app if both of the following conditions are
+ * true:
* <ul>
- * <li>Activity has requested orientation more than 2 times within 1-second timer
+ * <li>Activity has requested orientation more than two times within one-second timer
* <li>Activity is not letterboxed for fixed orientation
* </ul>
*
@@ -953,23 +956,21 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that it needs to be opted-out from the compatibility
+ * treatment that sandboxes the {@link android.view.View View} API.
*
* <p>The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
+ * {@link android.view.View View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen View#getLocationOnScreen()} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame View#getWindowVisibleDisplayFrame()}
* return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and
* letterbox modes.
*
* <p>Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
+ * opted-out from the "Sandbox View API to Activity bounds" treatment even if the device
+ * manufacturer has opted the app into the treatment.
*
* <p>Not setting this property at all, or setting this property to {@code true} has no effect.
*
@@ -987,12 +988,11 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the application can be opted-in or opted-out
- * from the compatibility treatment that enables sending a fake focus event for unfocused
- * resumed split screen activities. This is needed because some game engines wait to get
- * focus before drawing the content of the app which isn't guaranteed by default in multi-window
- * modes.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the application can be opted-in or opted-out from the
+ * compatibility treatment that enables sending a fake focus event for unfocused resumed
+ * split-screen activities. This is needed because some game engines wait to get focus before
+ * drawing the content of the app which isn't guaranteed by default in multi-window mode.
*
* <p>Device manufacturers can enable this treatment using their discretion on a per-device
* basis to improve display compatibility. The treatment also needs to be specifically enabled
@@ -1022,9 +1022,9 @@ public interface WindowManager extends ViewManager {
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the camera compatibility
+ * force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1034,10 +1034,11 @@ public interface WindowManager extends ViewManager {
* rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
* camera and is removed once camera is closed.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may apply the force rotation
* treatment to fixed orientation activities. Device manufacturers can exclude packages from the
@@ -1060,9 +1061,9 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded
- * from the activity "refresh" after the camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the activity "refresh"
+ * after the camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1079,10 +1080,11 @@ public interface WindowManager extends ViewManager {
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may "refresh" activity after
* the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1105,10 +1107,10 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be or shouldn't be
- * "refreshed" after the camera compatibility force rotation treatment using "paused ->
- * resumed" cycle rather than "stopped -> resumed".
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the activity should be or shouldn't be "refreshed" after
+ * the camera compatibility force rotation treatment using "paused -> resumed" cycle rather than
+ * "stopped -> resumed".
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1124,10 +1126,11 @@ public interface WindowManager extends ViewManager {
* values in apps (e.g., display or camera rotation) that influence camera preview and can lead
* to sideways or stretching issues persisting even after force rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
* cycle using their discretion to improve display compatibility.
@@ -1153,22 +1156,23 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * compatibility override for orientation set by the device manufacturer. When the orientation
- * override is applied it can:
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the compatibility
+ * override for orientation set by the device manufacturer. When the orientation override is
+ * applied it can:
* <ul>
* <li>Replace the specific orientation requested by the app with another selected by the
- device manufacturer, e.g. replace undefined requested by the app with portrait.
+ device manufacturer; for example, replace undefined requested by the app with portrait.
* <li>Always use an orientation selected by the device manufacturer.
* <li>Do one of the above but only when camera connection is open.
* </ul>
*
- * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+ * <p>This property is different from {@link #PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
* (which is used to avoid orientation loops caused by the incorrect use of {@link
- * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
- * orientation selected by the device manufacturer rather than ignoring one of orientation
- * requests coming from the app while respecting the previous one.
+ * android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}) because
+ * this property overrides the app to an orientation selected by the device manufacturer rather
+ * than ignoring one of orientation requests coming from the app while respecting the previous
+ * one.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
@@ -1190,10 +1194,10 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that fixes display orientation to landscape natural orientation when
- * an activity is fullscreen.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that fixes display orientation to landscape natural orientation when an activity is
+ * fullscreen.
*
* <p>When this compat override is enabled and while display is fixed to the landscape natural
* orientation, the orientation requested by the activity will be still respected by bounds
@@ -1202,16 +1206,17 @@ public interface WindowManager extends ViewManager {
* lanscape natural orientation.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
- * using their discretion to improve display compatibility on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * using their discretion to improve display compatibility on displays that have the ignore
+ * orientation request display setting enabled by OEMs on the device (enables compatibility mode
+ * for fixed orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system wiil use landscape display
* orientation when the following conditions are met:
* <ul>
* <li>Natural orientation of the display is landscape
- * <li>ignoreOrientationRequest display setting is enabled
+ * <li>ignore requested orientation display setting is enabled
* <li>Activity is fullscreen.
* <li>Device manufacturer enabled the treatment.
* </ul>
@@ -1233,9 +1238,9 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that changes the min aspect ratio.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that changes the min aspect ratio.
*
* <p>When this compat override is enabled the min aspect ratio given in the app's manifest can
* be overridden by the device manufacturer using their discretion to improve display
@@ -1264,14 +1269,14 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility overrides that change the resizability of the app.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * overrides that change the resizability of the app.
*
* <p>When these compat overrides are enabled they force the packages they are applied to to be
- * resizable / unresizable. If the app is forced to be resizable this won't change whether
- * the app can be put into multi-windowing mode, but allow the app to resize without going into
- * size-compat mode when the window container resizes, such as display size change or screen
+ * resizable/unresizable. If the app is forced to be resizable this won't change whether the app
+ * can be put into multi-windowing mode, but allow the app to resize without going into size
+ * compatibility mode when the window container resizes, such as display size change or screen
* rotation.
*
* <p>Setting this property to {@code false} informs the system that the app must be
@@ -1320,34 +1325,29 @@ public interface WindowManager extends ViewManager {
}
/**
- * Application-level
- * {@link android.content.pm.PackageManager.Property PackageManager.Property}
- * tag that specifies whether OEMs are permitted to provide activity
- * embedding split-rule configurations on behalf of the app.
+ * Application-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that specifies whether OEMs are permitted to provide activity embedding split-rule
+ * configurations on behalf of the app.
*
- * <p>If {@code true}, the system is permitted to override the app's
- * windowing behavior and implement activity embedding split rules, such as
- * displaying activities side by side. A system override informs the app
- * that the activity embedding APIs are disabled so the app will not provide
- * its own activity embedding rules, which would conflict with the system's
+ * <p>If {@code true}, the system is permitted to override the app's windowing behavior and
+ * implement activity embedding split rules, such as displaying activities side by side. A
+ * system override informs the app that the activity embedding APIs are disabled so the app
+ * doesn't provide its own activity embedding rules, which would conflict with the system's
* rules.
*
- * <p>If {@code false}, the system is not permitted to override the
- * windowing behavior of the app. Set the property to {@code false} if the
- * app provides its own activity embedding split rules, or if you want to
- * prevent the system override for any other reason.
+ * <p>If {@code false}, the system is not permitted to override the windowing behavior of the
+ * app. Set the property to {@code false} if the app provides its own activity embedding split
+ * rules, or if you want to prevent the system override for any other reason.
*
* <p>The default value is {@code false}.
*
- * <p class="note"><b>Note:</b> Refusal to permit the system override is not
- * enforceable. OEMs can override the app's activity embedding
- * implementation whether or not this property is specified and set to
- * <code>false</code>. The property is, in effect, a hint to OEMs.
+ * <p class="note"><b>Note:</b> Refusal to permit the system override is not enforceable. OEMs
+ * can override the app's activity embedding implementation whether or not this property is
+ * specified and set to {@code false}. The property is, in effect, a hint to OEMs.
*
- * <p>OEMs can implement activity embedding on any API level. The best
- * practice for apps is to always explicitly set this property in the app
- * manifest file regardless of targeted API level rather than rely on the
- * default value.
+ * <p>OEMs can implement activity embedding on any API level. The best practice for apps is to
+ * always explicitly set this property in the app manifest file regardless of targeted API level
+ * rather than rely on the default value.
*
* <p><b>Syntax:</b>
* <pre>
@@ -1362,14 +1362,15 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} that an app can specify to inform the system that the app is ActivityEmbedding
- * split feature enabled.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that an app can specify to inform the system that the app is activity embedding split feature
+ * enabled.
*
* <p>With this property, the system could provide custom behaviors for the apps that are
- * ActivityEmbedding split feature enabled. For example, the fixed-portrait orientation
+ * activity embedding split feature enabled. For example, the fixed-portrait orientation
* requests of the activities could be ignored by the system in order to provide seamless
- * ActivityEmbedding split experiences while holding the large-screen devices in landscape mode.
+ * activity embedding split experiences while holding large screen devices in landscape
+ * orientation.
*
* <p><b>Syntax:</b>
* <pre>
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410cf89c..6a5fc03fcc6e 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
package android.widget;
parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index bd7f5a0924cc..d9e76fefad7f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.app.Application;
import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.system.Os;
@@ -98,8 +103,10 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
@@ -124,7 +131,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -324,6 +333,13 @@ public class RemoteViews implements Parcelable, Filter {
(clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
/**
+ * The maximum waiting time for remote adapter conversion in milliseconds
+ *
+ * @hide
+ */
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+
+ /**
* Application that hosts the remote views.
*
* @hide
@@ -1042,28 +1058,96 @@ public class RemoteViews implements Parcelable, Filter {
}
private class SetRemoteCollectionItemListAdapterAction extends Action {
- private final RemoteCollectionItems mItems;
+ private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
- SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+ @NonNull RemoteCollectionItems items) {
viewId = id;
- mItems = items;
- mItems.setHierarchyRootData(getHierarchyRootData());
+ items.setHierarchyRootData(getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(items);
+ }
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+ viewId = id;
+ mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+ setHierarchyRootData(getHierarchyRootData());
+ }
+
+ private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+ Intent intent) {
+ if (intent == null) {
+ Log.e(LOG_TAG, "Null intent received when generating adapter future");
+ return CompletableFuture.completedFuture(new RemoteCollectionItems
+ .Builder().build());
+ }
+
+ final Context context = ActivityThread.currentApplication();
+ final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+ context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ result.defaultExecutor(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName,
+ IBinder iBinder) {
+ RemoteCollectionItems items;
+ try {
+ items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+ .getRemoteCollectionItems();
+ } catch (RemoteException re) {
+ items = new RemoteCollectionItems.Builder().build();
+ Log.e(LOG_TAG, "Error getting collection items from the factory",
+ re);
+ } finally {
+ context.unbindService(this);
+ }
+
+ result.complete(items);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) { }
+ });
+
+ result.completeOnTimeout(
+ new RemoteCollectionItems.Builder().build(),
+ MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+ return result;
}
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
viewId = parcel.readInt();
- mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(
+ new RemoteCollectionItems(parcel, getHierarchyRootData()));
}
@Override
public void setHierarchyRootData(HierarchyRootData rootData) {
- mItems.setHierarchyRootData(rootData);
+ mItemsFuture = mItemsFuture
+ .thenApply(rc -> {
+ rc.setHierarchyRootData(rootData);
+ return rc;
+ });
+ }
+
+ private static RemoteCollectionItems getCollectionItemsFromFuture(
+ CompletableFuture<RemoteCollectionItems> itemsFuture) {
+ RemoteCollectionItems items;
+ try {
+ items = itemsFuture.get();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error getting collection items from future", e);
+ items = new RemoteCollectionItems.Builder().build();
+ }
+
+ return items;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- mItems.writeToParcel(dest, flags, /* attached= */ true);
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.writeToParcel(dest, flags, /* attached= */ true);
}
@Override
@@ -1072,6 +1156,8 @@ public class RemoteViews implements Parcelable, Filter {
View target = root.findViewById(viewId);
if (target == null) return;
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1092,10 +1178,10 @@ public class RemoteViews implements Parcelable, Filter {
// recycling in setAdapter, so we must call setAdapter again if the number of view types
// increases.
if (adapter instanceof RemoteCollectionItemsAdapter
- && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
try {
((RemoteCollectionItemsAdapter) adapter).setData(
- mItems, params.handler, params.colorResources);
+ items, params.handler, params.colorResources);
} catch (Throwable throwable) {
// setData should never failed with the validation in the items builder, but if
// it does, catch and rethrow.
@@ -1105,7 +1191,7 @@ public class RemoteViews implements Parcelable, Filter {
}
try {
- adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+ adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
params.handler, params.colorResources));
} catch (Throwable throwable) {
// This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -4679,6 +4765,12 @@ public class RemoteViews implements Parcelable, Filter {
* providing data to the RemoteViewsAdapter
*/
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+ if (AppGlobals.getIntCoreSetting(
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+ return;
+ }
addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc01b9e..d4f4d19e9bad 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -43,6 +43,13 @@ public abstract class RemoteViewsService extends Service {
private static final Object sLock = new Object();
/**
+ * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
+ *
+ * @hide
+ */
+ private static final int MAX_NUM_ENTRY = 25;
+
+ /**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -227,6 +234,30 @@ public abstract class RemoteViewsService extends Service {
}
}
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+ .Builder().build();
+
+ try {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ mFactory.onDataSetChanged();
+
+ itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+ final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ for (int i = 0; i < numOfEntries; i++) {
+ itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ }
+
+ items = itemsBuilder.build();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return items;
+ }
+
private RemoteViewsFactory mFactory;
private boolean mIsCreated;
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9ffccb34f44d..0ba271f253fb 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -532,6 +532,17 @@ public final class SystemUiDeviceConfigFlags {
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
/**
+ * (boolean) Whether to enable the adapter conversion in RemoteViews
+ */
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+
+ /**
+ * Default value for whether the adapter conversion is enabled or not. This is set for
+ * RemoteViews and should not be a common practice.
+ */
+ public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
+ /**
* (boolean) Whether the task manager should show a stop button if the app is allowlisted
* by the user.
*/
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 000000000000..f177ef88c38f
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -0,0 +1,93 @@
+/*
+ * 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.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+ private static final List<SyncableFlag> sKnownFlags = new ArrayList<>();
+
+ public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+ public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+ public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+ public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+ /** Returns true if the passed in flag matches a flag in this class. */
+ public static boolean isCoreFlag(SyncableFlag flag) {
+ for (SyncableFlag knownFlag : sKnownFlags) {
+ if (knownFlag.getName().equals(flag.getName())
+ && knownFlag.getNamespace().equals(flag.getNamespace())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List<SyncableFlag> getCoreFlags() {
+ return sKnownFlags;
+ }
+
+ private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+ BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+ return f;
+ }
+
+ private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+ FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+ return f;
+ }
+
+ private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+ FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+ return f;
+ }
+
+ private static DynamicBooleanFlag dynamicBooleanFlag(
+ String namespace, String name, boolean defaultValue) {
+ DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+ return f;
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index a0e29347d07f..f8a5520c26d5 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -58,6 +58,7 @@ public class TimeoutRecord {
int APP_REGISTERED = 7;
int SHORT_FGS_TIMEOUT = 8;
int JOB_SERVICE = 9;
+ int APP_START = 10;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -186,4 +187,10 @@ public class TimeoutRecord {
public static TimeoutRecord forJobService(String reason) {
return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
}
+
+ /** Record for app startup timeout. */
+ @NonNull
+ public static TimeoutRecord forAppStart(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9f75d6..918d9c029ef5 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@ interface IRemoteViewsFactory {
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index e0183931777b..1b1efeed5ff2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1035,7 +1035,7 @@ public class LockPatternUtils {
CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery);
/**
- * Invalidate the credential cache
+ * Invalidate the credential type cache
* @hide
*/
public final static void invalidateCredentialTypeCache() {
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index d068a3a0f62e..e2672f5b03ba 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -21,3 +21,6 @@ per-file ImageFloatingTextView.java = file:/services/core/java/com/android/serve
per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
+
+# Appwidget related
+per-file *RemoteViews* = file:/services/appwidget/java/com/android/server/appwidget/OWNERS
diff --git a/core/proto/android/input/keyboard_configured.proto b/core/proto/android/input/keyboard_configured.proto
index 16990087b319..0b4bf8eb9171 100644
--- a/core/proto/android/input/keyboard_configured.proto
+++ b/core/proto/android/input/keyboard_configured.proto
@@ -47,4 +47,8 @@ message KeyboardLayoutConfig {
// IntDef annotation at:
// services/core/java/com/android/server/input/KeyboardMetricsCollector.java
optional int32 layout_selection_criteria = 4;
+ // Keyboard layout type provided by IME
+ optional int32 ime_layout_type = 5;
+ // Language tag provided by IME (e.g. en-US, ru-Cyrl etc.)
+ optional string ime_language_tag = 6;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a01c7b6355c1..10cf353bf5e9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7655,6 +7655,24 @@
<permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to read and synchronize non-core flags.
+ Apps without this permission can only read a subset of flags specifically intended
+ for use in "core", (i.e. third party apps). Apps with this permission can define their
+ own flags, and federate those values with other system-level apps.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.SYNC_FLAGS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.WRITE_FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 5ccae4a86029..f5e6948f186e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -671,7 +671,7 @@
<string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Sakatu hau aurpegi-eredua ezabatzeko eta, gero, gehitu aurpegia berriro"</string>
<string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguratu Aurpegi bidez desblokeatzea"</string>
<string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonoa desblokeatzeko, begira iezaiozu"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak &gt; Pribatutasuna atalean"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko eginbidea erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak &gt; Pribatutasuna atalean"</string>
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguratu telefonoa desblokeatzeko modu gehiago"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Sakatu hau hatz-marka bat gehitzeko"</string>
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Hatz-marka bidez desblokeatzea"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index d8689755ac75..729f77101bf5 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -256,7 +256,7 @@
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Usa esta opción na maioría das circunstancias. Permíteche realizar un seguimento do progreso do informe, introducir máis detalles sobre o problema e facer capturas de pantalla. É posible que omita algunhas seccións menos usadas para as que se tarda máis en facer o informe."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Informe completo"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Usa esta opción para que a interferencia sexa mínima cando o teu dispositivo non responda ou funcione demasiado lento, ou ben cando precises todas as seccións do informe. Non poderás introducir máis detalles nin facer máis capturas de pantalla."</string>
- <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundos.}}"</string>
+ <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundos.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Realizouse a captura de pantalla co informe de erros"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Produciuse un erro ao realizar a captura de pantalla co informe de erros"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo silencioso"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index c15240fa7b0d..bbcf5098d96d 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1370,7 +1370,7 @@
<string name="usb_power_notification_message" msgid="7284765627437897702">"연결된 기기를 충전합니다. 옵션을 더 보려면 탭하세요."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"아날로그 오디오 액세서리가 감지됨"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"연결된 기기가 이 휴대전화와 호환되지 않습니다. 자세히 알아보려면 탭하세요."</string>
- <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨"</string>
+ <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨."</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"USB 디버깅을 사용 중지하려면 탭하세요."</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB 디버깅을 사용하지 않으려면 선택합니다."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"무선 디버깅 연결됨"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 41f29300a128..9e0e3a2ea24d 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1941,7 +1941,7 @@
<string name="country_selection_title" msgid="5221495687299014379">"地區偏好設定"</string>
<string name="search_language_hint" msgid="7004225294308793583">"請輸入語言名稱"</string>
<string name="language_picker_section_suggested" msgid="6556199184638990447">"建議語言"</string>
- <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"建議的語言"</string>
+ <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"建議地區"</string>
<string name="language_picker_section_suggested_bilingual" msgid="5932198319583556613">"建議語言"</string>
<string name="region_picker_section_suggested_bilingual" msgid="704607569328224133">"建議地區"</string>
<string name="language_picker_section_all" msgid="1985809075777564284">"所有語言"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2d8bfbb8517d..66ff01e6e7b9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5236,7 +5236,7 @@
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
- <integer name="config_selected_udfps_touch_detection">3</integer>
+ <integer name="config_selected_udfps_touch_detection">0</integer>
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 08c40ba0e823..b7a5bc826641 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -151,6 +151,23 @@
<integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
<java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_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}
+ 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" />
+
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f795bd7cc3fd..c120af347741 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1780,6 +1780,10 @@
<string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
<string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
<string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 34332a5c5363..bffcf5f27ade 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2571,6 +2571,8 @@
<java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_dialog_default_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_face_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
<java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
@@ -4946,7 +4948,6 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
- <java-symbol type="string" name="vdm_pip_blocked" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
new file mode 100644
index 000000000000..3fc94394d12c
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@SmallTest
+@Presubmit
+public class FeatureFlagsTest {
+
+ IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake();
+ FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake);
+
+ @Before
+ public void setup() {
+ FeatureFlags.setInstance(mFeatureFlags);
+ }
+
+ @Test
+ public void testFusedOff_Disabled() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOn_Enabled() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultDisabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultEnabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultDisabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultEnabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_OverrideBeforeRead() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testFusedOffFlag_OverrideHasNoEffect() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOnFlag_OverrideHasNoEffect() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "false", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideBeforeRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideAfterRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ // Starts false
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_FiresListener() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ AtomicBoolean called = new AtomicBoolean(false);
+ FeatureFlags.ChangeListener listener = flag1 -> called.set(true);
+
+ mFeatureFlags.addChangeListener(listener);
+
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Fires listener.
+ assertThat(called.get()).isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
new file mode 100644
index 000000000000..bc5d8aa3ac73
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
@@ -0,0 +1,113 @@
+/*
+ * 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 android.flags;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class IFeatureFlagsFake implements IFeatureFlags {
+
+ private final Set<IFeatureFlagsCallback> mCallbacks = new HashSet<>();
+
+ List<SyncableFlag> mOverrides;
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides;
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides; }
+
+ @Override
+ public void overrideFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ mOverrides.add(syncableFlag);
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ private SyncableFlag findFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = null;
+ for (SyncableFlag sf : mOverrides) {
+ if (sf.getName().equals(syncableFlag.getName())
+ && sf.getNamespace().equals(syncableFlag.getNamespace())) {
+ match = sf;
+ break;
+ }
+ }
+
+ return match;
+ }
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void setFlagOverrides(List<SyncableFlag> flagList) {
+ mOverrides = flagList;
+ for (SyncableFlag sf : flagList) {
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(sf);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 184b9eac24f3..4f722cefcf9f 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -353,6 +353,23 @@ public class RemoteViewsAdapterTest {
public boolean isCreated() {
return false;
}
+
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ itemsBuilder.setHasStableIds(hasStableIds())
+ .setViewTypeCount(getViewTypeCount());
+ try {
+ for (int i = 0; i < mCount; i++) {
+ itemsBuilder.addItem(getItemId(i), getViewAt(i));
+ }
+ } catch (RemoteException e) {
+ // No-op
+ }
+
+ return itemsBuilder.build();
+ }
}
private static class DistinctIntent extends Intent {
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2c85fe4e9206..c4530f64a82d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -346,4 +346,8 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
+
+ <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
+ otherwise, it may not be able to enforce provision for managed devices. -->
+ <allow-in-power-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a45a8a183ac8..2eacaaf28bba 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -304,7 +304,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param context a proxy for the {@link android.view.Window} that contains the
- * {@link DisplayFeature}.
+ * {@link DisplayFeature}.
* @return a {@link List} of {@link DisplayFeature}s that are within the
* {@link android.view.Window} of the {@link Activity}
*/
@@ -336,10 +336,32 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
- if (!isZero(featureRect)) {
+ if (isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
- features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+ continue;
+ }
+ if (featureRect.left != 0 && featureRect.top != 0) {
+ throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+ + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+
+ }
+ if (featureRect.left == 0
+ && featureRect.width() != windowConfiguration.getBounds().width()) {
+ throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ }
+ if (featureRect.top == 0
+ && featureRect.height() != windowConfiguration.getBounds().height()) {
+ throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
}
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
return features;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6d14440c9b18..f8d7b6bc3aad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -202,17 +202,18 @@ class DesktopTasksController(
}
/**
- * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
- * startBounds
+ * The first part of the animated move to desktop transition. Applies the changes to move task
+ * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+ * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
*/
- fun moveToFreeform(
+ fun startMoveToDesktop(
taskInfo: RunningTaskInfo,
startBounds: Rect,
dragToDesktopValueAnimator: MoveToDesktopAnimator
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+ "DesktopTasksController: startMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -221,18 +222,21 @@ class DesktopTasksController(
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
- dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
- /** Brings apps to front and sets freeform task bounds */
- private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ /**
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+ */
+ private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktop with animation taskId=%d",
+ "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -241,8 +245,8 @@ class DesktopTasksController(
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
@@ -272,13 +276,14 @@ class DesktopTasksController(
}
/**
- * Move a task to fullscreen after being dragged from fullscreen and released back into
- * status bar area
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+ * and released back into status bar area.
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
+ fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelMoveToFreeform taskId=%d",
+ "DesktopTasksController: cancelMoveToDesktop taskId=%d",
task.taskId
)
val wct = WindowContainerTransaction()
@@ -784,7 +789,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ finalizeMoveToDesktop(taskInfo, freeformBounds)
}
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 650cac5cb999..1acf783257f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -79,7 +79,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
* @param wct WindowContainerTransaction for transition
* @param onAnimationEndCallback to be called after animation
*/
- public void startTransition(@WindowManager.TransitionType int type,
+ private void startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,17 +88,29 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
}
/**
- * Starts Transition of type TRANSIT_ENTER_FREEFORM
+ * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
* @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
* to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
- public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+ public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
@NonNull MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mMoveToDesktopAnimator = moveToDesktopAnimator;
- startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+ startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
+ }
+
+ /**
+ * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
}
/**
@@ -155,7 +167,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_ENTER_FREEFORM
+ if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// Transitioning to freeform but keeping fullscreen bounds, so the crop is set
// to null and we don't require an animation
@@ -182,7 +194,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
}
Rect endBounds = change.getEndAbsBounds();
- if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !endBounds.isEmpty()) {
// This Transition animates a task to freeform bounds after being dragged into freeform
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 75659960bc32..4ca383f66267 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -149,11 +149,13 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+ /** Transition type for starting the move to desktop mode. */
+ public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 10;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+ /** Transition type for finalizing the move to desktop mode. */
+ public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 11;
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 1a18fc2d7546..80cf96a93efa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -197,7 +197,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
@@ -616,7 +616,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (mMoveToDesktopAnimator != null) {
relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator = null;
return;
@@ -643,7 +643,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> c.moveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
mDragToDesktopAnimationStartBounds,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator.startAnimation();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
index fd56a6e49d3e..8a3c2c975faa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
@@ -42,7 +42,7 @@ import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import org.junit.Assert.assertNotNull
-internal object SplitScreenUtils {
+object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 0f9579d58929..69c8ecd5644d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.system.helpers.CommandsHelper
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTestData
@@ -29,15 +28,18 @@ import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+
import org.junit.Assume
import org.junit.Before
+import org.junit.Rule
abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- lateinit var cmdHelper: CommandsHelper
- private lateinit var letterboxStyle: HashMap<String, String>
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -52,50 +54,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
@Before
fun before() {
- cmdHelper = CommandsHelper.getInstance(instrumentation)
- Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
- letterboxStyle = mapLetterboxStyle()
- resetLetterboxStyle()
- setLetterboxEducationEnabled(false)
- }
-
- @After
- fun after() {
- resetLetterboxStyle()
- }
-
- private fun mapLetterboxStyle(): HashMap<String, String> {
- val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
- val lines = res.lines()
- val map = HashMap<String, String>()
- for (line in lines) {
- val keyValuePair = line.split(":")
- if (keyValuePair.size == 2) {
- val key = keyValuePair[0].trim()
- map[key] = keyValuePair[1].trim()
- }
- }
- return map
- }
-
- private fun getLetterboxStyle(): HashMap<String, String> {
- if (!::letterboxStyle.isInitialized) {
- letterboxStyle = mapLetterboxStyle()
- }
- return letterboxStyle
- }
-
- private fun resetLetterboxStyle() {
- cmdHelper.executeShellCommand("wm reset-letterbox-style")
- }
-
- private fun setLetterboxEducationEnabled(enabled: Boolean) {
- cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
- }
-
- private fun isIgnoreOrientationRequest(): Boolean {
- val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
- return res != null && res.contains("true")
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
@@ -115,7 +74,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
/** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
private fun assumeLetterboxRoundedCornersEnabled() {
- Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+ Assume.assumeTrue(letterboxRule.hasCornerRadius)
}
fun assertLetterboxAppVisibleAtStartAndEnd() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 000000000000..5a1136f97c6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+ private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private lateinit var _letterboxStyle: MutableMap<String, String>
+
+ val letterboxStyle: Map<String, String>
+ get() {
+ if (!::_letterboxStyle.isInitialized) {
+ _letterboxStyle = mapLetterboxStyle()
+ }
+ return _letterboxStyle
+ }
+
+ val cornerRadius: Int?
+ get() = asInt(letterboxStyle["Corner radius"])
+
+ val hasCornerRadius: Boolean
+ get() {
+ val radius = cornerRadius
+ return radius != null && radius > 0
+ }
+
+ val isIgnoreOrientationRequest: Boolean
+ get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ resetLetterboxStyle()
+ _letterboxStyle = mapLetterboxStyle()
+ val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
+ var hasLetterboxEducationStateChanged = false
+ if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
+ hasLetterboxEducationStateChanged = true
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ withLetterboxEducationEnabled)
+ }
+ return try {
+ object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ base!!.evaluate()
+ }
+ }
+ } finally {
+ if (hasLetterboxEducationStateChanged) {
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ isLetterboxEducationEnabled
+ )
+ }
+ resetLetterboxStyle()
+ }
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = execAdb("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun resetLetterboxStyle() {
+ execAdb("wm reset-letterbox-style")
+ }
+
+ private fun asInt(str: String?): Int? = try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index a7bd2584ba23..67d5718e6c1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 000000000000..e6ca261a317f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ * are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+ }
+ teardown {
+ letterboxTranslucentApp.exit(wmHelper)
+ letterboxTranslucentLauncherApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Checks the transparent activity is launched on top of the opaque one
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+ .then()
+ .isAppWindowOnTop(letterboxTranslucentApp)
+ }
+ }
+
+ /**
+ * Checks that the activity is letterboxed
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLetterboxed() {
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+ }
+
+ /**
+ * Checks that the translucent activity inherits bounds from the opaque one.
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+ flicker.assertLayersEnd {
+ this.visibleRegion(letterboxTranslucentApp)
+ .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+ }
+ }
+
+ /**
+ * Checks that the translucent activity has rounded corners
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityHasRoundedCorners() {
+ flicker.assertLayersEnd {
+ this.hasRoundedCorners(letterboxTranslucentApp)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory
+ .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index e875aae431a1..68fa8d2fc2e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -32,7 +32,9 @@ import org.junit.runners.Parameterized
/**
* Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
*
- * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions:
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
*
* ```
* Launch a fixed portrait app in landscape to letterbox app
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index a18a144b4bf1..fcb6931af9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 000000000000..ea0392cee95a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+ }
+
+ protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+ device.wait(
+ Until.findObject(By.text("Launch Transparent")),
+ FIND_TIMEOUT
+ )
+
+ protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index c6642f3472f0..885ae3851c6d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -99,16 +99,17 @@ public class EnterDesktopTaskTransitionHandlerTest {
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+ .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
mEnterDesktopTaskTransitionHandler);
doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
- mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+ change);
assertTrue(mEnterDesktopTaskTransitionHandler
@@ -120,17 +121,18 @@ public class EnterDesktopTaskTransitionHandlerTest {
@Test
public void testTransitEnterDesktopModeAnimation() throws Throwable {
- final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+ final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
change.setEndAbsBounds(new Rect(0, 0, 1, 1));
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+ TransitionInfo info = createTransitionInfo(
+ Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
runOnUiThread(() -> {
try {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index f71e7289bd37..b87002371775 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -88,6 +88,9 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
validateCache(identity, size);
mInitialized = true;
+ if (identity != nullptr && size > 0 && mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
}
}
@@ -96,11 +99,6 @@ void ShaderCache::setFilename(const char* filename) {
mFilename = filename;
}
-BlobCache* ShaderCache::getBlobCacheLocked() {
- LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
- return mBlobCache.get();
-}
-
sk_sp<SkData> ShaderCache::load(const SkData& key) {
ATRACE_NAME("ShaderCache::load");
size_t keySize = key.size();
@@ -115,8 +113,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
if (!valueBuffer) {
return nullptr;
}
- BlobCache* bc = getBlobCacheLocked();
- size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
int maxTries = 3;
while (valueSize > mObservedBlobValueSize && maxTries > 0) {
mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
return nullptr;
}
valueBuffer = newValueBuffer;
- valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
maxTries--;
}
if (!valueSize) {
@@ -143,16 +140,17 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
- switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (mBlobCache->set(key, keySize, value, valueSize)) {
case BlobCache::InsertResult::kInserted:
// This is what we expect/hope. It means the cache is large enough.
return;
case BlobCache::InsertResult::kDidClean: {
ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
valueSize);
+ if (mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
return;
}
case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,15 +170,10 @@ void set(BlobCache* cache, const void* key, size_t keySize, const void* value, s
}
}
}
-} // namespace
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache) {
- if (mIDHash.size()) {
- auto key = sIDKey;
- set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
- }
// The most straightforward way to make ownership shared
mMutex.unlock();
mMutex.lock_shared();
@@ -209,11 +202,10 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /
const void* value = data.data();
- BlobCache* bc = getBlobCacheLocked();
if (mInStoreVkPipelineInProgress) {
if (mOldPipelineCacheSize == -1) {
// Record the initial pipeline cache size stored in the file.
- mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+ mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
}
if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
// There has not been change in pipeline cache size. Stop trying to save.
@@ -228,7 +220,7 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- set(bc, key.data(), keySize, value, valueSize);
+ set(key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 2f91c778b8a0..74955503dbb1 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -96,20 +96,18 @@ private:
void operator=(const ShaderCache&) = delete;
/**
- * "getBlobCacheLocked" returns the BlobCache object being used to store the
- * key/value blob pairs. If the BlobCache object has not yet been created,
- * this will do so, loading the serialized cache contents from disk if
- * possible.
- */
- BlobCache* getBlobCacheLocked() REQUIRES(mMutex);
-
- /**
* "validateCache" updates the cache to match the given identity. If the
* cache currently has the wrong identity, all entries in the cache are cleared.
*/
bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
/**
+ * Helper for BlobCache::set to trace the result and ensure the identity hash
+ * does not get evicted.
+ */
+ void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
+
+ /**
* "saveToDiskLocked" attempts to save the current contents of the cache to
* disk. If the identity hash exists, we will insert the identity hash into
* the cache for next validation.
@@ -127,11 +125,9 @@ private:
bool mInitialized GUARDED_BY(mMutex) = false;
/**
- * "mBlobCache" is the cache in which the key/value blob pairs are stored. It
- * is initially NULL, and will be initialized by getBlobCacheLocked the
- * first time it's needed.
- * The blob cache contains the Android build number. We treat version mismatches as an empty
- * cache (logic implemented in BlobCache::unflatten).
+ * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+ * The blob cache contains the Android build number. We treat version mismatches
+ * as an empty cache (logic implemented in BlobCache::unflatten).
*/
std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 31a92ac5ab23..46698a6fdcc0 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -40,7 +40,7 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 19> sEnableExtensions{
+static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -60,6 +60,7 @@ static std::array<std::string_view, 19> sEnableExtensions{
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+ VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 9b33704cc8e7..eccf6047b90c 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -191,6 +191,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+ && isPendingIntentValid(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
@@ -276,6 +278,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
return "not default data subscription";
+ case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED:
+ return "notifications disabled";
case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
return "notification shown";
@@ -321,26 +325,45 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
}
private void onDisplayPerformanceBoostNotification(@NonNull Context context,
- @NonNull Intent intent, boolean repeat) {
- if (!repeat && !isIntentValid(intent)) {
+ @NonNull Intent intent, boolean localeChanged) {
+ if (!localeChanged && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
Resources res = getResources(context);
- NotificationChannel channel = new NotificationChannel(
- PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
- res.getString(R.string.performance_boost_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT);
- // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
- // to allow users to disable notifications posted to this channel without affecting other
- // notifications in this application.
- channel.setBlockable(true);
- context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ NotificationChannel channel = notificationManager.getNotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+ res.getString(R.string.performance_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ // CarrierDefaultApp notifications are unblockable by default.
+ // Make this channel blockable to allow users to disable notifications posted to this
+ // channel without affecting other notifications in this application.
+ channel.setBlockable(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ } else if (localeChanged) {
+ // If the channel already exists but the locale has changed, update the channel name.
+ channel.setName(res.getString(R.string.performance_boost_notification_channel));
+ }
- String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
+ boolean channelNotificationsDisabled =
+ channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) {
+ // If notifications are disabled for the app or channel, fail the purchase request.
+ logd("Purchase request failed because notifications are disabled for the "
+ + (channelNotificationsDisabled ? "channel." : "application."));
+ sendSlicePurchaseAppResponse(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
+ return;
+ }
+ String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
Notification notification =
new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
.setContentTitle(res.getString(
@@ -369,11 +392,12 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
- logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+ logd((localeChanged ? "Update" : "Display")
+ + " the performance boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
- if (!repeat) {
+ if (!localeChanged) {
sIntents.put(capability, intent);
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 61847b517c8d..3c8ef6ed0550 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -72,6 +73,7 @@ public class SlicePurchaseBroadcastReceiverTest {
@Mock PendingIntent mContentIntent1;
@Mock PendingIntent mContentIntent2;
@Mock PendingIntent mNotificationShownIntent;
+ @Mock PendingIntent mNotificationsDisabledIntent;
@Mock Context mContext;
@Mock Resources mResources;
@Mock Configuration mConfiguration;
@@ -90,6 +92,7 @@ public class SlicePurchaseBroadcastReceiverTest {
doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
+ doReturn(true).when(mNotificationManager).areNotificationsEnabled();
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mSpiedResources).when(mContext).getResources();
@@ -221,12 +224,10 @@ public class SlicePurchaseBroadcastReceiverTest {
doReturn(true).when(mPendingIntent).isBroadcast();
doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
anyString(), eq(PendingIntent.class));
- doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
- .getCreatorPackage();
- doReturn(true).when(mNotificationShownIntent).isBroadcast();
- doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
- eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
- eq(PendingIntent.class));
+ createValidPendingIntent(mNotificationShownIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ createValidPendingIntent(mNotificationsDisabledIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
// spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -253,6 +254,12 @@ public class SlicePurchaseBroadcastReceiverTest {
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
}
+ private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
+ doReturn(true).when(intent).isBroadcast();
+ doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
+ }
+
@Test
public void testNotificationCanceled() {
// send ACTION_NOTIFICATION_CANCELED
@@ -335,4 +342,22 @@ public class SlicePurchaseBroadcastReceiverTest {
clearInvocations(mConfiguration);
return captor.getValue();
}
+
+ @Test
+ public void testNotificationsDisabled() throws Exception {
+ doReturn(false).when(mNotificationManager).areNotificationsEnabled();
+
+ displayPerformanceBoostNotification();
+
+ // verify notification was not shown
+ verify(mNotificationManager, never()).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ any(),
+ eq(UserHandle.ALL));
+ verify(mNotificationShownIntent, never()).send();
+
+ // verify SlicePurchaseController was notified that notifications are disabled
+ verify(mNotificationsDisabledIntent).send();
+ }
}
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 3b980c5a5770..17d5e1d7fb34 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -208,7 +208,7 @@
<item msgid="2675263395797191850">"Animazioa desaktibatuta"</item>
<item msgid="5790132543372767872">"Animazio-eskala: 0,5x"</item>
<item msgid="2529692189302148746">"Animazio-eskala: 1×"</item>
- <item msgid="8072785072237082286">"Animazio-eskala: 1,5x"</item>
+ <item msgid="8072785072237082286">"Animazio-eskala: 1,5×"</item>
<item msgid="3531560925718232560">"Animazio-eskala 2x"</item>
<item msgid="4542853094898215187">"Animazio-eskala: 5x"</item>
<item msgid="5643881346223901195">"Animazio-eskala: 10x"</item>
@@ -217,7 +217,7 @@
<item msgid="3376676813923486384">"Animazioa desaktibatuta"</item>
<item msgid="753422683600269114">"Animazio-eskala: 0,5x"</item>
<item msgid="3695427132155563489">"Animazio-eskala: 1×"</item>
- <item msgid="9032615844198098981">"Animazio-eskala: 1,5x"</item>
+ <item msgid="9032615844198098981">"Animazio-eskala: 1,5×"</item>
<item msgid="8473868962499332073">"Animazio-eskala: 2x"</item>
<item msgid="4403482320438668316">"Animazio-eskala: 5x"</item>
<item msgid="169579387974966641">"Animazio-eskala: 10x"</item>
@@ -226,7 +226,7 @@
<item msgid="6416998593844817378">"Animazioa desaktibatuta"</item>
<item msgid="875345630014338616">"Animazio-eskala: 0,5x"</item>
<item msgid="2753729231187104962">"Animazio-eskala: 1×"</item>
- <item msgid="1368370459723665338">"Animazio-eskala: 1,5x"</item>
+ <item msgid="1368370459723665338">"Animazio-eskala: 1,5×"</item>
<item msgid="5768005350534383389">"Animazio-eskala: 2x"</item>
<item msgid="3728265127284005444">"Animazio-eskala: 5x"</item>
<item msgid="2464080977843960236">"Animazio-eskala: 10x"</item>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d345db..6b0a9060d782 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@ public class SystemSettings {
Settings.System.TTY_MODE,
Settings.System.MASTER_MONO,
Settings.System.MASTER_BALANCE,
+ Settings.System.STAY_AWAKE_ON_FOLD,
Settings.System.SOUND_EFFECTS_ENABLED,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.POWER_SOUNDS_ENABLED, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 753c860bd5d6..a08d07e1d778 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,6 +218,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR);
diff --git a/packages/Shell/res/values-gl/strings.xml b/packages/Shell/res/values-gl/strings.xml
index 912dc85e15e4..9d4f7de68793 100644
--- a/packages/Shell/res/values-gl/strings.xml
+++ b/packages/Shell/res/values-gl/strings.xml
@@ -20,7 +20,7 @@
<string name="bugreport_notification_channel" msgid="2574150205913861141">"Informes de erros"</string>
<string name="bugreport_in_progress_title" msgid="4311705936714972757">"Estase xerando o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
<string name="bugreport_finished_title" msgid="4429132808670114081">"Rexistrouse o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
- <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erro"</string>
+ <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erros"</string>
<string name="bugreport_updating_wait" msgid="3322151947853929470">"Agarda..."</string>
<string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"O informe de erros aparecerá no teléfono en breve"</string>
<string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selecciona para compartir o teu informe de erros"</string>
@@ -32,7 +32,7 @@
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Non mostrar outra vez"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Informes de erros"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Non se puido ler o ficheiro de informe de erros"</string>
- <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erro ao ficheiro zip"</string>
+ <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erros ao ficheiro zip"</string>
<string name="bugreport_unnamed" msgid="2800582406842092709">"sen nome"</string>
<string name="bugreport_info_action" msgid="2158204228510576227">"Detalles"</string>
<string name="bugreport_screenshot_action" msgid="8677781721940614995">"Captura de pantalla"</string>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 01e6bf00a664..bb8002a18c11 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -53,6 +53,20 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ },
+ {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
@@ -131,5 +145,21 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 8e79e3ce1742..38b99cc5f5ee 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -70,6 +70,9 @@ class ViewHierarchyAnimator {
* If a new layout change happens while an animation is already in progress, the animation
* is updated to continue from the current values to the new end state.
*
+ * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
+ * entry in this set, changes to that view will not be animated.
+ *
* The animator continues to respond to layout changes until [stopAnimating] is called.
*
* Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@ class ViewHierarchyAnimator {
fun animate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = false)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = false,
+ excludedViews = excludedViews
+ )
}
/**
@@ -95,16 +105,24 @@ class ViewHierarchyAnimator {
fun animateNextUpdate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = true)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = true,
+ excludedViews = excludedViews
+ )
}
private fun animate(
rootView: View,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ excludedViews: Set<View> = emptySet()
): Boolean {
if (
!occupiesSpace(
@@ -119,7 +137,7 @@ class ViewHierarchyAnimator {
}
val listener = createUpdateListener(interpolator, duration, ephemeral)
- addListener(rootView, listener, recursive = true)
+ addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
return true
}
@@ -921,8 +939,11 @@ class ViewHierarchyAnimator {
private fun addListener(
view: View,
listener: View.OnLayoutChangeListener,
- recursive: Boolean = false
+ recursive: Boolean = false,
+ excludedViews: Set<View> = emptySet()
) {
+ if (excludedViews.contains(view)) return
+
// Make sure that only one listener is active at a time.
val previousListener = view.getTag(R.id.tag_layout_listener)
if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@ class ViewHierarchyAnimator {
view.setTag(R.id.tag_layout_listener, listener)
if (view is ViewGroup && recursive) {
for (i in 0 until view.childCount) {
- addListener(view.getChildAt(i), listener, recursive = true)
+ addListener(
+ view.getChildAt(i),
+ listener,
+ recursive = true,
+ excludedViews = excludedViews
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ca91b8a21a81..38b751c9445d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -17,15 +17,19 @@
package com.android.systemui.notifications.ui.composable
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@ fun Notifications(
) {
// TODO(b/272779828): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(16.dp),
) {
- Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Notifications",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Shelf",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 665d6dd0cfa2..1bb341c76e69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -17,15 +17,19 @@
package com.android.systemui.qs.footer.ui.compose
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@ fun QuickSettings(
) {
// TODO(b/272780058): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(16.dp),
) {
- Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Quick settings",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "QS footer actions",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 20e175160aa6..27358f53aaf2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,18 +16,19 @@
package com.android.systemui.shade.ui.composable
+import androidx.compose.foundation.clickable
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.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -62,12 +63,7 @@ class ShadeScene(
override fun Content(
containerName: String,
modifier: Modifier,
- ) {
- ShadeScene(
- viewModel = viewModel,
- modifier = modifier,
- )
- }
+ ) = ShadeScene(viewModel, modifier)
private fun destinationScenes(
up: SceneKey,
@@ -84,23 +80,15 @@ private fun ShadeScene(
viewModel: ShadeSceneViewModel,
modifier: Modifier = Modifier,
) {
- // TODO(b/280887022): implement the real UI.
-
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Shade", style = MaterialTheme.typography.headlineMedium)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(
- onClick = { viewModel.onContentClicked() },
- ) {
- Text("Open some content")
- }
- }
- }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = modifier.height(160.dp))
+ Notifications(modifier = modifier.weight(1f))
}
}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 18753fd9c0c7..006fc09fb400 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -422,9 +422,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -696,7 +693,6 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c0b69c169ccd..25f77ea4e6d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -172,7 +172,6 @@ public interface QSTile {
public String expandedAccessibilityClassName;
public SlashState slash;
public boolean handlesLongClick = true;
- public boolean showRippleEffect = true;
@Nullable
public Drawable sideViewCustomDrawable;
public String spec;
@@ -217,7 +216,6 @@ public interface QSTile {
|| !Objects.equals(other.dualTarget, dualTarget)
|| !Objects.equals(other.slash, slash)
|| !Objects.equals(other.handlesLongClick, handlesLongClick)
- || !Objects.equals(other.showRippleEffect, showRippleEffect)
|| !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
other.spec = spec;
other.icon = icon;
@@ -234,7 +232,6 @@ public interface QSTile {
other.isTransient = isTransient;
other.slash = slash != null ? slash.copy() : null;
other.handlesLongClick = handlesLongClick;
- other.showRippleEffect = showRippleEffect;
other.sideViewCustomDrawable = sideViewCustomDrawable;
return changed;
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 57b3acd6557a..66c54f2a668e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,6 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:importantForAccessibility="no"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
deleted file mode 100644
index 9304ff72f054..000000000000
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
- constraints. -->
-<com.android.systemui.util.animation.TransitionLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/media_recommendations"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:forceHasOverlappingRendering="false"
- android:background="@drawable/qs_media_background"
- android:theme="@style/MediaPlayer">
-
- <!-- This view just ensures the full media player is a certain height. -->
- <View
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded" />
-
- <com.android.internal.widget.CachingIconView
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_app_icon_size"
- android:layout_height="@dimen/qs_media_app_icon_size"
- android:minWidth="@dimen/qs_media_app_icon_size"
- android:minHeight="@dimen/qs_media_app_icon_size"
- android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <FrameLayout
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover1"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover2"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover3"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <include
- layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 2fde9479d42a..a33625212d34 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -76,6 +76,13 @@
android:layout_height="match_parent"
android:visibility="invisible" />
+ <!-- Shared container for the notification stack. Can be positioned by either
+ the keyguard_root_view or notification_panel -->
+ <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+ android:id="@+id/shared_notification_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index ab52465be63c..3baae3376bd0 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -15,6 +15,7 @@
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.SystemUI.QuickSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 5bd2184a2608..e0c25e3ca3fb 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekyk"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Kon nie skermopname stoor nie"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Bekyk tans volskerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swiep van bo af as jy wil uitgaan."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Het dit"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Tuis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Kieslys"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Wanneer jy deel, opneem of uitsaai, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Wanneer jy ’n app deel, opneem of uitsaai, het Android toegang tot enigiets wat in daardie app gewys of gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begin"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f2eb7669c4a7..b8ac5fc74ca6 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ለመመልከት መታ ያድርጉ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"የማያ ገጽ ቀረጻን ማስቀመጥ ላይ ስህተት"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገፅ ቀረጻን መጀመር ላይ ስህተት"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ሙሉ ገፅ በማሳየት ላይ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ገባኝ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ተመለስ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"መነሻ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ምናሌ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ጀምር"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገፅ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 9a74144ce79e..e4657735ae85 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"انقر لعرض التسجيل."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"حدث خطأ أثناء حفظ تسجيل محتوى الشاشة."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"جارٍ العرض بملء الشاشة"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"حسنًا"</string>
<string name="accessibility_back" msgid="6530104400086152611">"رجوع"</string>
<string name="accessibility_home" msgid="5430449841237966217">"الرئيسية"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"القائمة"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏أثناء المشاركة أو التسجيل أو البثّ، يمكن لنظام Android الوصول إلى كل المحتوى المعروض على شاشتك أو الذي يتم تشغيله على جهازك، لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏أثناء مشاركة محتوى تطبيق أو تسجيله أو بثّه، يمكن لنظام Android الوصول إلى كل المحتوى المعروض أو الذي يتم تشغيله في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"بدء"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 856989cd400b..372cab8e9816 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"চাবলৈ টিপক"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ৰেকৰ্ড কৰা স্ক্ৰীন ছেভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"পূৰ্ণ স্ক্ৰীনত চাই আছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বাহিৰ হ’বলৈ ওপৰৰ পৰা তললৈ ছোৱাইপ কৰক।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুজি পালোঁ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"উভতি যাওক"</string>
<string name="accessibility_home" msgid="5430449841237966217">"গৃহ পৃষ্ঠাৰ বুটাম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান হোৱা যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"আৰম্ভ কৰক"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 1c14b04ebf05..ba87178ad5f4 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Baxmaq üçün toxunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran çəkimini yadda saxlayarkən xəta oldu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekrana baxış"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana səhifə"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşım, qeydəalma və ya yayım zamanı Android-in ekranda görünən, yaxud cihazda oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Tətbiq paylaşdıqda, qeydə aldıqda və ya yayımladıqda Android-in həmin tətbiqdə göstərilən, yaxud oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlayın"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index c00cd6a6dd33..258ae1d2d215 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da biste pregledali"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška pri čuvanju snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se ceo ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da biste izašli, prevucite nadole odozgo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Važi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 95eebada5318..5f9d63ccfb4a 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Націсніце для прагляду"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Памылка захавання запісу экрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Прагляд у поўнаэкранным рэжыме"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Для выхаду правядзіце зверху ўніз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Зразумела"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"На Галоўную старонку"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Калі адбываецца абагульванне, запіс ці трансляцыя, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Калі адбываецца абагульванне, запіс ці трансляцыя змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Пачаць"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 567a22b606b5..f2a8dbf1bc9d 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Докоснете за преглед"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при запазването на записа на екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Изглед на цял екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За изход плъзнете пръст надолу от горната част."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Разбрах"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Начало"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когато споделяте, записвате или предавате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когато споделяте, записвате или предавате дадено приложение, Android има достъп до всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Стартиране"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 38308e274a41..836b0edfe736 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"দেখতে ট্যাপ করুন"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"স্ক্রিন রেকর্ডিং সেভ করার সময় কোনও সমস্যা হয়েছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ফুল-স্ক্রিনে দেখা হচ্ছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বেরিয়ে যেতে উপর থেকে নিচের দিকে সোয়াইপ করুন।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুঝেছি"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ফিরুন"</string>
<string name="accessibility_home" msgid="5430449841237966217">"হোম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময়, স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো সব কিছুই Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপনি কোনও অ্যাপ শেয়ার, রেকর্ড বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা চালানো হয় এমন সব কিছু Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"শুরু করুন"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index eaa0d9d1dec2..db902ef0b2b2 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da vidite"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška prilikom pohranjivanja snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se cijeli ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da izađete, prevucite odozgo nadolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumijem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Dugme za početnu stranicu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Dugme Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 6416543d3f1b..7eefa4fbee87 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca per veure-la"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"S\'ha produït un error en desar la gravació de la pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Mode de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per sortir, llisca cap avall des de la part superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entesos"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Enrere"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inici"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inicia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 56ae4b8ca0a3..4a070fbafe84 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Klepnutím nahrávku zobrazíte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Při ukládání záznamu obrazovky došlo k chybě"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazení celé obrazovky"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Režim ukončíte přejetím prstem shora dolů."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Rozumím"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zpět"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domů"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Během sdílení, nahrávání nebo odesílání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Během sdílení, nahrávání nebo odesílání aplikace má Android přístup k veškerému obsahu, který je v dané aplikaci zobrazen nebo přehráván. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začít"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 0ce993226988..2893b1e94a0e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryk for at se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Skærmoptagelsen kunne ikke gemmes"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fuld skærm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Stryg ned fra toppen for at afslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK, det er forstået"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbage"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hjem"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, optager eller caster, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, optager eller caster en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index ef76528af1a7..b52754117dcf 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zum Ansehen tippen"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fehler beim Speichern der Bildschirmaufzeichnung"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vollbildmodus wird aktiviert"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zum Beenden von oben nach unten wischen"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zurück"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startbildschirm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Beim Teilen, Aufnehmen oder Streamen hat Android Zugriff auf alle Inhalte, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Beim Teilen, Aufnehmen oder Streamen einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder von ihr wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2c4b33221d44..e23cbb8b5a34 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Πατήστε για προβολή"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Σφάλμα κατά την αποθήκευση της εγγραφής οθόνης"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Προβολή σε πλήρη οθόνη"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Το κατάλαβα"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Πίσω"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Αρχική οθόνη"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Μενού"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Έναρξη"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Η κοινοποίηση τίθεται σε παύση κατά την εναλλαγή μεταξύ εφαρμογών"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Εναλλακτικά, κοινοποιήστε την εφαρμογή"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Επιστροφή"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Εναλλαγή μεταξύ εφαρμογών"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 03d2a510ed68..809873974955 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f328508efc74..d20838262722 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 03d2a510ed68..809873974955 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 03d2a510ed68..809873974955 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index ed958d839e7a..8a321e2434d8 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎Tap to view‎‏‎‎‏‎"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎Error saving screen recording‎‏‎‎‏‎"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎Error starting screen recording‎‏‎‎‏‎"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎Viewing full screen‎‏‎‎‏‎"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎To exit, swipe down from the top.‎‏‎‎‏‎"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎Got it‎‏‎‎‏‎"</string>
<string name="accessibility_back" msgid="6530104400086152611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎Back‎‏‎‎‏‎"</string>
<string name="accessibility_home" msgid="5430449841237966217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎Home‎‏‎‎‏‎"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‎Menu‎‏‎‎‏‎"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎Start‎‏‎‎‏‎"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎Sharing pauses when you switch apps‎‏‎‎‏‎"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎Share this app instead‎‏‎‎‏‎"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎Switch back‎‏‎‎‏‎"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎App switch‎‏‎‎‏‎"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎Blocked by your IT admin‎‏‎‎‏‎"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎Screen capturing is disabled by device policy‎‏‎‎‏‎"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎Clear all‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 91b6be6c1529..a6edfd56711b 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Presiona para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Se produjo un error al guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página principal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartas, grabes o transmitas contenido, Android podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartas, grabes o transmitas una app, Android podrá acceder a todo el contenido que se muestre o que reproduzcas en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 178c8b15cc7a..2fb76cda3a6f 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para verla"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"No se ha podido guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Modo de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo de arriba abajo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Empezar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de aplicación"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
@@ -1030,7 +1037,7 @@
<string name="status_before_loading" msgid="1500477307859631381">"El contenido se mostrará en breve"</string>
<string name="missed_call" msgid="4228016077700161689">"Llamada perdida"</string>
<string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
- <string name="people_tile_description" msgid="8154966188085545556">"Consulta los mensajes recientes, las llamadas perdidas y los cambios de estado"</string>
+ <string name="people_tile_description" msgid="8154966188085545556">"Consulta mensajes recientes, llamadas perdidas y cambios de estado"</string>
<string name="people_tile_title" msgid="6589377493334871272">"Conversación"</string>
<string name="paused_by_dnd" msgid="7856941866433556428">"Pausado por No molestar"</string>
<string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> ha enviado un mensaje: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index b2470f7be334..b300e78505da 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Puudutage kuvamiseks"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Viga ekraanisalvestise salvestamisel"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Kuvamine täisekraanil"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Väljumiseks pühkige ülevalt alla."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selge"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tagasi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Kodu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menüü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kui jagate, salvestate või kannate üle, on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kui jagate, salvestate või kannate rakendust üle, on Androidil juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite, fotode, heli ja videoga ettevaatlik."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Alusta"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 33fee8d24398..8fd391c3ca20 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Sakatu ikusteko"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore bat gertatu da pantaila-grabaketa gordetzean"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Pantaila osoko ikuspegia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Irteteko, pasatu hatza goitik behera."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ados"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atzera"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hasiera"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menua"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Hasi"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 97f6930afcac..4d3b3f00a2ea 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"برای مشاهده ضربه بزنید"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"خطا در ذخیره‌سازی ضبط صفحه‌نمایش"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحه‌نمایش"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمام‌صفحه"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"برای خروج، از بالای صفحه تند به‌پایین بکشید."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجه‌ام"</string>
<string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string>
<string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"منو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوا هستید، Android به همه محتوایی که در صفحه‌تان نمایان است یا در دستگاهتان پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوای برنامه‌ای هستید، Android به همه محتوایی که در آن برنامه نمایان است یا پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحه‌نمایش» به‌دلیل خط‌مشی دستگاه غیرفعال است"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index abf52fef2ae1..f3b95de8c5d5 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Napauta näyttääksesi"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Virhe näyttötallenteen tallentamisessa"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Koko näytön tilassa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selvä"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen sovelluksella näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Aloita"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 4f94d1f2249a..8a74f8a7d1ff 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez vers le bas à partir du haut."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Précédent"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domicile"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou diffusez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou diffusez une application, Android a accès à tout ce qui est visible sur votre écran ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 0ede09a263bb..5fe8f558d123 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Appuyez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur lors de l\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage en plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Retour"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Accueil"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou castez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou castez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqué par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 144675563f77..804aeca026af 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para ver o contido"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Produciuse un erro ao gardar a gravación da pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vendo pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Volver"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cando compartes, gravas ou emites contido, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cando compartes, gravas ou emites unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
@@ -1040,7 +1051,7 @@
<string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Produciuse un problema ao ler o medidor da batería"</string>
<string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string>
<string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string>
- <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir bloqueo de pantalla"</string>
+ <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string>
<string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string>
<string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
<string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 89390b14732f..cfd5a360c85d 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"જોવા માટે ટૅપ કરો"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"સ્ક્રીન રેકોર્ડિંગ સાચવવામાં ભૂલ આવી"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"સમજાઈ ગયું"</string>
<string name="accessibility_back" msgid="6530104400086152611">"પાછળ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"હોમ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"મેનુ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"શરૂ કરો"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fb017da39dee..194321af397e 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"देखने के लिए टैप करें"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रिकॉर्डिंग सेव करते समय गड़बड़ी हुई"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"फ़ुल स्क्रीन मोड पर देखा जा रहा है"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहर निकलने के लिए, सबसे ऊपर से नीचे की ओर स्वाइप करें."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ठीक है"</string>
<string name="accessibility_back" msgid="6530104400086152611">"वापस जाएं"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेन्यू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास स्क्रीन पर दिख रहे कॉन्टेंट या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास उस ऐप्लिकेशन पर दिख रहे कॉन्टेंट या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"शुरू करें"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index b7a7cdd1785c..7fbc100fc585 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite za prikaz"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pogreška prilikom spremanja snimke zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Gledanje preko cijelog zaslona"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Shvaćam"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Natrag"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izbornik"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kad dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kad dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 6a96fccc6a1c..1c46c441a04f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koppintson a megtekintéshez"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hiba történt a képernyőrögzítés mentése során"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Megtekintése teljes képernyőn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Értem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Vissza"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Főoldal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az Android az adott alkalmazásban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Indítás"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d91b3dd24c28..afd8b66dc859 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտում"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Պարզ է"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Հետ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Տուն"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Ցանկ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ տեսանելի է ձեր էկրանին և նվագարկվում է ձեր սարքում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Սկսել"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 550b04855ae8..f5564ba4b446 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Terjadi error saat menyimpan rekaman layar"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat layar penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, geser layar ke bawah dari atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Mengerti"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Utama"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Jika Anda membagikan, merekam, atau mentransmisikan, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mulai"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 86455c08931b..647d1c49af71 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ýttu til að skoða"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Villa við að vista skjáupptöku"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Notar allan skjáinn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Strjúktu niður frá efri brún til að hætta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ég skil"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Til baka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Heim"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valmynd"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Þegar þú deilir, tekur upp eða varpar hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Þegar þú deilir, tekur upp eða varpar forriti hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Byrja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 17ec328856ae..3b60f427e211 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tocca per visualizzare"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore durante il salvataggio della registrazione dello schermo"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per uscire, scorri dall\'alto verso il basso."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando condividi, registri o trasmetti, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando condividi, registri o trasmetti un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inizia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
@@ -510,14 +521,14 @@
<string name="stream_accessibility" msgid="3873610336741987152">"Accessibilità"</string>
<string name="volume_ringer_status_normal" msgid="1339039682222461143">"Attiva suoneria"</string>
<string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Attiva vibrazione"</string>
- <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Disattiva suoneria"</string>
+ <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenzia"</string>
<string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tocca per riattivare l\'audio."</string>
<string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tocca per attivare la vibrazione. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tocca per disattivare l\'audio. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tocca per attivare la vibrazione."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tocca per disattivare l\'audio."</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tocca per cambiare la modalità della suoneria"</string>
- <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"disattiva l\'audio"</string>
+ <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenzia"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"riattiva l\'audio"</string>
<string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"vibrazione"</string>
<string name="volume_dialog_title" msgid="6502703403483577940">"Controlli del volume %s"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 5b317cbf9db7..121e5be22a2e 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"יש להקיש כדי להציג"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"שגיאה בשמירה של הקלטת המסך"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"צפייה במסך מלא"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"הבנתי"</string>
<string name="accessibility_back" msgid="6530104400086152611">"חזרה"</string>
<string name="accessibility_home" msgid="5430449841237966217">"בית"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"תפריט"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"התחלה"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏האפשרות נחסמה על ידי אדמין ב-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 56a78e28d0e0..383d9f481952 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"タップすると表示されます"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"画面の録画の保存中にエラーが発生しました"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"全画面表示"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"終了するには、上から下にスワイプします。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"戻る"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ホーム"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"メニュー"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"共有、録画、キャスト中は、画面に表示される内容やデバイスで再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"アプリの共有、録画、キャスト中は、そのアプリで表示または再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f19b6b634ea2..943b288558c6 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"შეეხეთ სანახავად"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ეკრანის ჩანაწერის შენახვისას შეცდომა მოხდა"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"მიმდინარეობს სრულ ეკრანზე ნახვა"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"გასვლისთვის გადაფურცლეთ ზემოდან ქვემოთ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"გასაგებია"</string>
<string name="accessibility_back" msgid="6530104400086152611">"უკან"</string>
<string name="accessibility_home" msgid="5430449841237966217">"საწყისი"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"მენიუ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს ან იკვრება აპში. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"დაწყება"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index c7d30e79286c..a80dcfbb936b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көру үшін түртіңіз."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран жазбасын сақтау кезінде қате шықты."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толық экранда көру"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түсінікті"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артқа"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үй"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Mәзір"</string>
@@ -301,8 +304,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> дейін"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы уақытында"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы уақыты аяқталғанға дейін"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы режимінде"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы режимі аяқталғанға дейін"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлісу, жазу не трансляциялау кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде Android жүйесі онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Бастау"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазарту"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 375b79d5acf0..702b9cba0bb4 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ចុចដើម្បីមើល"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"មានបញ្ហាក្នុងការរក្សាទុក​ការថតវីដេអូអេក្រង់"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហា​ក្នុងការ​ចាប់ផ្ដើម​ថត​អេក្រង់"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"កំពុងមើលពេញអេក្រង់"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"យល់ហើយ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ថយក្រោយ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"គេហ​ទំព័រ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ម៉ឺនុយ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬចាក់នៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់កម្មវិធី, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលបង្ហាញ ឬចាក់នៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ចាប់ផ្ដើម"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទ​ដោយគោលការណ៍ឧបករណ៍"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 437f40675feb..544af6a7c787 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್‌ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ಅರ್ಥವಾಯಿತು"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ಹಿಂದೆ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ಮುಖಪುಟ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ಮೆನು"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ಪ್ರಾರಂಭಿಸಿ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 991695144024..bd58c0fed370 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"탭하여 보기"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"화면 녹화 저장 중에 오류가 발생했습니다."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"전체 화면 모드"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"종료하려면 위에서 아래로 스와이프합니다."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"확인"</string>
<string name="accessibility_back" msgid="6530104400086152611">"뒤로"</string>
<string name="accessibility_home" msgid="5430449841237966217">"홈"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"메뉴"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"공유, 녹화 또는 전송 중에 Android가 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"앱을 공유, 녹화 또는 전송할 때는 Android가 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"시작"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index aaec2cdb6bc3..054aa2ea8e34 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көрүү үчүн таптаңыз"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран тартылган жок"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толук экран режимин көрүү"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түшүндүм"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артка"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үйгө"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлүшүп, жаздырып же тышкы экранга чыгарып жатканда Android экраныңыздагы бардык маалыматты же түзмөктө ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Колдонмону бөлүшүп, жаздырып же тышкы экранга чыгарганда Android ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Баштоо"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 0306fc91d85e..66bd9e3636fc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ແຕະເພື່ອເບິ່ງ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ເກີດຂໍ້ຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ກຳລັງ​ເບິ່ງ​ເຕັມ​​ຈໍ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ເພື່ອອອກ, ໃຫ້ປັດລົງຈາກເທິງສຸດ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ເຂົ້າໃຈແລ້ວ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ກັບຄືນ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ໜ້າທຳອິດ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ເມນູ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ແອັບດັ່ງກ່າວ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ເລີ່ມ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8548a24ec73d..c93506f98aa5 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Palieskite, kad peržiūrėtumėte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Išsaugant ekrano įrašą įvyko klaida"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Peržiūrima viso ekrano režimu"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Supratau"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atgal"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Pagrindinis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kai bendrinate, įrašote ar perduodate turinį, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kai bendrinate, įrašote ar perduodate programą, „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pradėti"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 79f73b681ab8..dcd54c499c5f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Pieskarieties, lai skatītu"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Saglabājot ekrāna ierakstu, radās kļūda."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Skatīšanās pilnekrāna režīmā"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Lai izietu, no augšdaļas velciet lejup."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Labi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atpakaļ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Sākums"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izvēlne"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Sākt"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index b8829c6ccf58..e83bf8165ffd 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Допрете за прегледување"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при зачувувањето на снимката од екранот"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Се прикажува на цел екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За да излезете, повлечете одозгора надолу."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Сфатив"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна страница"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Кога споделувате, снимате или емитувате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Кога споделувате, снимате или емитувате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Започни"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index f8c14e6d3b60..c8fbde84853b 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിക്കുന്നതിൽ പിശക്"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"പൂർണ്ണ സ്‌ക്രീനിൽ കാണുന്നു"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"പുറത്തുകടക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"മനസ്സിലായി"</string>
<string name="accessibility_back" msgid="6530104400086152611">"മടങ്ങുക"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ഹോം"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"മെനു"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ആരംഭിക്കുക"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ ബ്ലോക്ക് ചെയ്‌തു"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്‌ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index ceb87821b4a7..15745ff24304 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Харахын тулд товшино уу"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Дэлгэцийн бичлэгийг хадгалахад алдаа гарлаа"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Бүтэн дэлгэцээр үзэж байна"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ойлголоо"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Буцах"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Гэрийн"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Цэс"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android таны дэлгэцэд харуулсан эсвэл төхөөрөмжид тань тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android тухайн аппад харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг бусад зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Эхлүүлэх"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index ba45d533d614..a52d217871c6 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"पाहण्यासाठी टॅप करा"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रेकॉर्डिंग सेव्ह करताना एरर आली"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूर्ण स्क्रीनवर पाहत आहात"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"समजले"</string>
<string name="accessibility_back" msgid="6530104400086152611">"मागे"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तुम्ही एखादे अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरुवात करा"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्‍क्रीन कॅप्‍चर करणे बंद केले आहे"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index e28ea016af97..3da42a54c497 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketik untuk lihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ralat semasa menyimpan rakaman skrin"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat skrin penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, leret ke bawah dari bahagian atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Rumah"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Apabila anda membuat perkongsian, rakaman atau penghantaran, Android boleh mengakses apa-apa sahaja yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Apabila anda berkongsi, merakam atau menghantar apl, Android boleh mengakses apa-apa sahaja yang ditunjukan atau dimainkan pada apl tersebut. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mula"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 5bcd5a09efb5..c258862513e3 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ကြည့်ရှုရန် တို့ပါ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ဖန်သားပြင်ရိုက်ကူးမှုကို သိမ်းရာတွင် အမှားရှိသည်"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ဖန်သားပြင်အပြည့် ကြည့်နေသည်"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ထွက်ရန် အပေါ်မှ အောက်သို့ ပွတ်ဆွဲပါ။"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"နားလည်ပြီ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"နောက်သို့"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ပင်မစာမျက်နှာ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"မီနူး"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"အက်ပ်တစ်ခုဖြင့် မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"စတင်ရန်"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 755ee81a4d7a..9277d523f145 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trykk for å se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Feil ved lagring av skjermopptaket"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fullskjerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sveip ned fra toppen for å avslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Skjønner"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbake"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startside"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, tar opp eller caster noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, tar opp eller caster en app, har Android tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begynn"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d1445e1e9c16..9f9bf85b25f7 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"हेर्नका लागि ट्याप गर्नुहोस्"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रिन रेकर्डिङ सेभ गर्ने क्रममा त्रुटि भयो"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूरा पर्दा हेर्दै"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"बुझेँ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"पछाडि"</string>
<string name="accessibility_home" msgid="5430449841237966217">"गृह"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनु"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तपाईंले कुनै एप सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले उक्त एपमा देखाइने वा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरु गर्नुहोस्"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका IT एड्मिनले ब्लक गर्नुभएको छ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 932889b8ba77..5c8ba847e906 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekijken"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fout bij opslaan van schermopname"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Volledig scherm wordt getoond"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startscherm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 8061fd6ae217..e41e7c21e616 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ସେଭ କରିବାରେ ତ୍ରୁଟି"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ବାହାରି ଯିବା ପାଇଁ, ଶୀର୍ଷରୁ ତଳକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ବୁଝିଗଲି"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ଫେରନ୍ତୁ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ହୋମ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ମେନୁ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ଆପଣ ଏକ ଆପ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0f3ef6af3a77..cc6377558dae 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋ ਗਈ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ਸਮਝ ਲਿਆ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ਪਿੱਛੇ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ਘਰ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ਮੀਨੂ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 757ffcd338c5..21018f9976cb 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Kliknij, aby wyświetlić"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Podczas zapisywania nagrania ekranu wystąpił błąd"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Włączony pełny ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Aby wyjść, przesuń palcem z góry na dół."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Wróć"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekran główny"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Rozpocznij"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 4ee6bd2175b3..82015ea8136a 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f16b0272114b..45853b9923d4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao guardar a gravação de ecrã"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização de ecrã inteiro"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Anterior"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando está a partilhar, gravar ou transmitir conteúdo, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando está a partilhar, gravar ou transmitir uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A partilha é pausada quando muda de app"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partilhar antes esta app"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Mudança de app"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 4ee6bd2175b3..82015ea8136a 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e57eb5d6cb52..bc6a5fd6ada3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Eroare la salvarea înregistrării ecranului"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vizualizare pe ecran complet"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pentru a ieși, glisează de sus în jos."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Când permiți accesul, înregistrezi sau proiectezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, Android are acces la orice se afișează pe ecran sau se redă în aplicație. Prin urmare, ai grijă cu informații cum ar fi parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Începe"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 70f6e1598680..80b0d0fac060 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Нажмите, чтобы посмотреть."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не удалось сохранить запись видео с экрана."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Полноэкранный режим"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чтобы выйти, проведите по экрану сверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ОК"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Главный экран"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когда вы демонстрируете, транслируете экран или записываете видео с него, система Android получает доступ ко всему, что видно или воспроизводится на устройстве. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когда вы демонстрируете, записываете или транслируете экран приложения, система Android получает доступ ко всему, что видно или воспроизводится в нем. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Начать"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index b4dc0f27cfe2..8989b7ef0b90 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"බැලීමට තට්ටු කරන්න"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"තිර පටිගත කිරීම සුරැකීමේ දෝෂයකි"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"මුළු තිරය බලමින්"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"වැටහුණි"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ආපසු"</string>
<string name="accessibility_home" msgid="5430449841237966217">"මුල් පිටුව"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"මෙනුව"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"අරඹන්න"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්‍රතිපත්තිය මගින් තිර ග්‍රහණය කිරීම අබල කර ඇත"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 802bd7a06123..e743d5604afc 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zobrazte klepnutím"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pri ukladaní nahrávky obrazovky sa vyskytla chyba"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazenie na celú obrazovku"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukončíte potiahnutím zhora nadol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Dobre"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Späť"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Plocha"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Počas zdieľania, nahrávania alebo prenosu bude mať Android prístup k všetkému, čo sa zobrazuje na obrazovke alebo prehráva v zariadení. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Počas zdieľania, nahrávania alebo prenosu v aplikácii bude mať Android prístup k všetkému zobrazovanému alebo prehrávaného obsahu v danej aplikácii. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začať"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a303e20ff5d4..90d62cb6b101 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dotaknite se za ogled."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Napaka pri shranjevanju posnetka zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vklopljen je celozaslonski način."</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazaj"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Začetni zaslon"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Pri deljenju, snemanju ali predvajanju ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Pri deljenju, snemanju ali predvajanju aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 4d0dcef5603b..3677bdee43b0 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trokit për të parë"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Gabim gjatë ruajtjes së regjistrimit të ekranit"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Po shikon ekranin e plotë"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Për të dalë, rrëshqit nga lart poshtë."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"E kuptova"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Faqja bazë"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyja"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kur ti ndan, regjistron ose transmeton, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kur ti ndan, regjistron ose transmeton një aplikacion, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Nis"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 6227d5d8c06f..19d30658509d 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Додирните да бисте прегледали"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при чувању снимка екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Приказује се цео екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Да бисте изашли, превуците надоле одозго."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Важи"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7cf096f43ca3..ae199e9ebb10 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryck för att visa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Det gick inte att spara skärminspelningen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visar på fullskärm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Svep nedåt från skärmens överkant för att avsluta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tillbaka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startsida"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"När du delar, spelar in eller castar har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"När du delar, spelar in eller castar en app har Android åtkomst till allt som visas eller spelas upp i appen. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Börja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 892d369e4330..abed50d4981c 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Unatazama kwenye skrini nzima"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ili uondoke, telezesha kidole kutoka juu hadi chini."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nimeelewa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nyuma"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Nyumbani"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Unaposhiriki, kurekodi au kutuma, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Unaposhiriki, kurekodi au kutuma programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Anza"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3378e1345938..8d930a5f8d2a 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"பார்க்கத் தட்டவும்"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ஸ்கிரீன் ரெக்கார்டிங்கைச் சேமிப்பதில் பிழை ஏற்பட்டது"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"முழுத் திரையில் காட்டுகிறது"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"புரிந்தது"</string>
<string name="accessibility_back" msgid="6530104400086152611">"பின்செல்"</string>
<string name="accessibility_home" msgid="5430449841237966217">"முகப்பு"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"மெனு"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் திரையில் காட்டப்படுகின்ற அல்லது சாதனத்தில் பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"நீங்கள் ஓர் ஆப்ஸைப் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படுகின்ற அல்லது பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"தொடங்கு"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 9e5054851af8..f90a43546a90 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"చూడటానికి ట్యాప్ చేయండి"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"స్క్రీన్ రికార్డింగ్‌ను సేవ్ చేయడంలో ఎర్రర్ ఏర్పడింది"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ఫుల్ స్క్రీన్‌లో చూస్తున్నారు"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"నిష్క్రమించడానికి, పై నుండి కిందికి స్వైప్ చేయండి."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"సరే"</string>
<string name="accessibility_back" msgid="6530104400086152611">"వెనుకకు"</string>
<string name="accessibility_home" msgid="5430449841237966217">"హోమ్"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"మెనూ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్‌పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"మీరు ఏదైనా యాప్‌ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్‌లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ప్రారంభించండి"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 554324a17824..804379210885 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"แตะเพื่อดู"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"เกิดข้อผิดพลาดในการบันทึกหน้าจอ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"กำลังดูแบบเต็มหน้าจอ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"รับทราบ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"กลับ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"หน้าแรก"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"เมนู"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"เริ่ม"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 2c632ec496d7..835c86438df9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"I-tap para tingnan"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Nagka-error sa pag-save ng recording ng screen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Panonood sa full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nakuha ko"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Simulan"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 84307715df44..3f05efa76bda 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Görüntülemek için dokunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran kaydı saklanırken hata oluştu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran olarak görüntüleme"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana sayfa"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşma, kaydetme veya yayınlama özelliğini kullandığınızda Android, ekranınızda gösterilen veya cihazınızda oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Bir uygulamayı paylaştığınızda, kaydettiğinizde veya yayınladığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlat"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index b3ee948da161..d4b9c345bb29 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Натисніть, щоб переглянути"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не вдалося зберегти запис відео з екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Перегляд на весь екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Щоб вийти, проведіть пальцем зверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Головна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Коли ви показуєте, записуєте або транслюєте екран, ОС Android отримує доступ до всього, що відображається на ньому чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Коли ви показуєте, записуєте або транслюєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Почати"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3f47187fa3c1..acf1357d7cd8 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"دیکھنے کے لیے تھپتھپائیں"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"اسکرین ریکارڈنگ محفوظ کرنے میں خرابی"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"فُل اسکرین میں دیکھ رہے ہیں"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"باہر نکلنے کیلئے اوپر سے نیچے کی طرف سوائپ کریں۔"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"سمجھ آ گئی"</string>
<string name="accessibility_back" msgid="6530104400086152611">"واپس جائیں"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ہوم"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"مینیو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏جب آپ اشتراک، ریکارڈنگ یا کسی ایپ کو کاسٹ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع کریں"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏آپ کے IT منتظم نے مسدود کر دیا"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 499eb1b435c6..2af9e922c843 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koʻrish uchun bosing"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran yozuvi saqlanmadi"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Butun ekranli rejim"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Chiqish uchun tepadan pastga torting."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Orqaga"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Uyga"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Ulashish, yozib olish va translatsiya qilish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Ilovani ulashish, yozib olish yoki translatsiya qilayotganingizda Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Boshlash"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 1d96b1ebfd64..791337dd60f6 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Nhấn để xem"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Có lỗi xảy ra khi lưu video ghi màn hình"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Xem toàn màn hình"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Quay lại"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Trang chủ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Khi bạn chia sẻ, ghi hoặc truyền, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ các thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Bắt đầu"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 0699551dc81a..bc289ee4244b 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"点按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"保存屏幕录制内容时出错"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"目前处于全屏模式"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"要退出,请从顶部向下滑动。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主屏幕"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"菜单"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"在分享内容时,Android 可以访问屏幕上显示或设备中播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"在分享、录制或投放内容时,Android 可以访问通过此应用显示或播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"开始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 7160b5600274..8db6dbd8062d 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"開啟全螢幕"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"由頂部向下滑動即可退出。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"首頁"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄影或投放時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄影或投放應用程式時,Android 可存取顯示在該應用程式中顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被你的 IT 管理員封鎖"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0deba3372662..9ba2aa9d8660 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕觸即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影內容時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從畫面頂端向下滑動。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主畫面"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄製或投放內容時,Android 將可存取畫面上顯示的任何資訊或裝置播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄製或投放內容時,Android 可存取應用程式中顯示的任何資訊或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index eb8ee45d3278..0e6bde25a1e6 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Thepha ukuze ubuke"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Iphutha lokulondoloza okokuqopha iskrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Ukubuka isikrini esigcwele"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ngiyezwa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Emuva"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekhaya"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Imenyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Uma wabelana, ukurekhoda, noma ukusakaza, i-Android inokufinyelela kunoma yini ebonakala esikrinini sakho noma okudlalwayo kudivayisi yakho. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Uma wabelana, ukurekhoda, noma ukusakaza ku-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwa kuleyo app. Ngakho-ke qaphela ngezinto ezfana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Qala"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 47cd1e707557..3366f4f6d443 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -663,9 +663,6 @@
<dimen name="restricted_padlock_pading">4dp</dimen>
- <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
- <dimen name="qs_peek_height">0dp</dimen>
-
<!-- Padding between subtitles and the following text in the QSFooter dialog -->
<dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 134a7a96bbc3..3a2177a0045c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -211,6 +211,7 @@
<item type="id" name="keyguard_indication_area" />
<item type="id" name="keyguard_indication_text" />
<item type="id" name="keyguard_indication_text_bottom" />
+ <item type="id" name="nssl_guideline" />
<item type="id" name="lock_icon" />
<item type="id" name="lock_icon_bg" />
</resources>
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
deleted file mode 100644
index b7d4b3aac079..000000000000
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto" >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_collapsed"
- />
-
- <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
- the same as the constraints in media_recommendations_expanded.xml. But, due to how
- ConstraintSets work, all the constraints need to be in the same place. So, the shared
- constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
- the constraints are instead duplicated between here and media_recommendations_expanded.xml.
- Ditto for the other cover containers. -->
- <Constraint
- android:id="@+id/media_cover1_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- android:visibility="gone"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
deleted file mode 100644
index ce25a7d01bf7..000000000000
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded"
- />
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_title1"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title1"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title2"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title2"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title3"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title3"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
index d3be3c7de5ad..d3be3c7de5ad 100644
--- a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
index 88c70552e9e8..88c70552e9e8 100644
--- a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
index 635f0fa44234..50e5466d0325 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
@@ -12,6 +12,10 @@ class KeyguardClockFrame(
) : FrameLayout(context, attrs) {
private var drawAlpha: Int = 255
+ init {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ }
+
protected override fun onSetAlpha(alpha: Int): Boolean {
drawAlpha = alpha
return true
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2377057f1fc5..d9b7bde66c67 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -69,7 +69,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
(long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
0.6f /* translationScale */,
0.45f /* delayScale */, AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.fast_out_linear_in));
+ mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 38c07dc98471..2bdf46e1309d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -104,8 +104,10 @@ public class KeyguardPatternView extends KeyguardInputView
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
private void updateMargins() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8f03eede1b1e..84f1da01c5c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -164,13 +164,13 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.DetectionStatus;
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
@@ -1471,27 +1471,31 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private FaceAuthenticationListener mFaceAuthenticationListener =
new FaceAuthenticationListener() {
@Override
- public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
- if (status instanceof AcquiredAuthenticationStatus) {
+ public void onAuthenticationStatusChanged(
+ @NonNull FaceAuthenticationStatus status
+ ) {
+ if (status instanceof AcquiredFaceAuthenticationStatus) {
handleFaceAcquired(
- ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
- } else if (status instanceof ErrorAuthenticationStatus) {
- ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+ ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo());
+ } else if (status instanceof ErrorFaceAuthenticationStatus) {
+ ErrorFaceAuthenticationStatus error =
+ (ErrorFaceAuthenticationStatus) status;
handleFaceError(error.getMsgId(), error.getMsg());
- } else if (status instanceof FailedAuthenticationStatus) {
+ } else if (status instanceof FailedFaceAuthenticationStatus) {
handleFaceAuthFailed();
- } else if (status instanceof HelpAuthenticationStatus) {
- HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+ } else if (status instanceof HelpFaceAuthenticationStatus) {
+ HelpFaceAuthenticationStatus helpMsg =
+ (HelpFaceAuthenticationStatus) status;
handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
- } else if (status instanceof SuccessAuthenticationStatus) {
+ } else if (status instanceof SuccessFaceAuthenticationStatus) {
FaceManager.AuthenticationResult result =
- ((SuccessAuthenticationStatus) status).getSuccessResult();
+ ((SuccessFaceAuthenticationStatus) status).getSuccessResult();
handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
}
}
@Override
- public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+ public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
}
};
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 444491f53629..a9779663cc7c 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
@@ -64,17 +64,22 @@ interface AuthenticationRepository {
val isUnlocked: StateFlow<Boolean>
/**
- * 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.
+ * Whether the auto confirm feature is enabled for the currently-selected user.
+ *
+ * Note that the length of the PIN is also important to take into consideration, please see
+ * [hintedPinLength].
*/
- val isBypassEnabled: StateFlow<Boolean>
-
- /** Whether the auto confirm feature is enabled for the currently-selected user. */
val isAutoConfirmEnabled: StateFlow<Boolean>
- /** The length of the PIN for which we should show a hint. */
+ /**
+ * The exact length a PIN should be for us to enable PIN length hinting.
+ *
+ * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
+ * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled.
+ *
+ * Note that PIN length hinting is only available if the PIN auto confirmation feature is
+ * available.
+ */
val hintedPinLength: Int
/** Whether the pattern should be visible for the currently-selected user. */
@@ -100,9 +105,6 @@ interface AuthenticationRepository {
*/
suspend fun isLockscreenEnabled(): Boolean
- /** See [isBypassEnabled]. */
- fun setBypassEnabled(isBypassEnabled: Boolean)
-
/** Reports an authentication attempt. */
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
@@ -144,7 +146,7 @@ constructor(
private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {
- override val isUnlocked: StateFlow<Boolean> = keyguardRepository.isKeyguardUnlocked
+ override val isUnlocked = keyguardRepository.isKeyguardUnlocked
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
@@ -153,9 +155,6 @@ constructor(
}
}
- private val _isBypassEnabled = MutableStateFlow(false)
- override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
-
override val isAutoConfirmEnabled: StateFlow<Boolean> =
userRepository.selectedUserInfo
.map { it.id }
@@ -166,10 +165,10 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = true,
+ initialValue = false,
)
- override val hintedPinLength: Int = LockPatternUtils.MIN_AUTO_PIN_REQUIREMENT_LENGTH
+ override val hintedPinLength: Int = 6
override val isPatternVisible: StateFlow<Boolean> =
userRepository.selectedUserInfo
@@ -212,10 +211,6 @@ constructor(
}
}
- override fun setBypassEnabled(isBypassEnabled: Boolean) {
- _isBypassEnabled.value = isBypassEnabled
- }
-
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
val selectedUserId = userRepository.selectedUserId
withContext(backgroundDispatcher) {
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 82674bff6433..b482977bde67 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
@@ -24,6 +24,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.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -37,7 +38,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -52,6 +52,7 @@ constructor(
private val repository: AuthenticationRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
+ private val keyguardRepository: KeyguardRepository,
private val clock: SystemClock,
) {
/**
@@ -77,14 +78,6 @@ constructor(
initialValue = true,
)
- /**
- * 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.
- */
- val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
@@ -103,10 +96,11 @@ constructor(
/** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
val hintedPinLength: StateFlow<Int?> =
- flow { emit(repository.getPinLength()) }
- .map { currentPinLength ->
- // Hinting is enabled for 6-digit codes only
- currentPinLength.takeIf { repository.hintedPinLength == it }
+ repository.isAutoConfirmEnabled
+ .map { isAutoConfirmEnabled ->
+ repository.getPinLength().takeIf {
+ isAutoConfirmEnabled && it == repository.hintedPinLength
+ }
}
.stateIn(
scope = applicationScope,
@@ -156,6 +150,16 @@ constructor(
}
/**
+ * 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
@@ -218,11 +222,6 @@ constructor(
return authenticationResult.isSuccessful
}
- /** See [isBypassEnabled]. */
- fun toggleBypassEnabled() {
- repository.setBypassEnabled(!repository.isBypassEnabled.value)
- }
-
/** Starts refreshing the throttling state every second. */
private suspend fun startThrottlingCountdown() {
cancelCountdown()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7a2f2443dbd2..9df56fcce430 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -250,7 +250,7 @@ public class AuthContainerView extends LinearLayout
.setMessage(messageBody)
.setPositiveButton(android.R.string.ok, null)
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
@@ -263,7 +263,7 @@ public class AuthContainerView extends LinearLayout
.setOnDismissListener(
dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 37ce44488346..083e21fbdfba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -17,12 +17,7 @@ package com.android.systemui.biometrics
import android.app.ActivityTaskManager
import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Color
import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -33,27 +28,23 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.util.Log
-import android.util.RotationUtils
import android.view.Display
import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
-import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -64,6 +55,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -86,6 +78,7 @@ constructor(
@Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
@Application private val scope: CoroutineScope,
dumpManager: DumpManager
) : Dumpable {
@@ -250,105 +243,15 @@ constructor(
private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
- val display = context.display!!
- // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
- display.getDisplayInfo(displayInfo)
- val offsets =
- sensorProps.getLocation(display.uniqueId).let { location ->
- if (location == null) {
- Log.w(TAG, "No location specified for display: ${display.uniqueId}")
- }
- location ?: sensorProps.location
- }
- overlayOffsets = offsets
-
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
- view.rotation =
- display.asSideFpsAnimationRotation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
- lottie.setAnimation(
- display.asSideFpsAnimation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
+ SideFpsOverlayViewBinder.bind(
+ view = view,
+ viewModel = sideFpsOverlayViewModelFactory.get(),
+ overlayViewParams = overlayViewParams,
+ reason = reason,
+ context = context,
)
- lottie.addLottieOnCompositionLoadedListener {
- // Check that view is not stale, and that overlayView has not been hidden/removed
- if (overlayView != null && overlayView == view) {
- updateOverlayParams(display, it.bounds)
- }
- }
orientationReasonListener.reason = reason
- lottie.addOverlayDynamicColor(context, reason)
-
- /**
- * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
- * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
- * in focus
- */
- view.setAccessibilityDelegate(
- object : AccessibilityDelegate() {
- override fun dispatchPopulateAccessibilityEvent(
- host: View,
- event: AccessibilityEvent
- ): Boolean {
- return if (
- event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- ) {
- true
- } else {
- super.dispatchPopulateAccessibilityEvent(host, event)
- }
- }
- }
- )
}
-
- @VisibleForTesting
- fun updateOverlayParams(display: Display, bounds: Rect) {
- val isNaturalOrientation = display.isNaturalOrientation()
- val isDefaultOrientation =
- if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
- val size = windowManager.maximumWindowMetrics.bounds
-
- val displayWidth = if (isDefaultOrientation) size.width() else size.height()
- val displayHeight = if (isDefaultOrientation) size.height() else size.width()
- val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
- val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
-
- val sensorBounds =
- if (overlayOffsets.isYAligned()) {
- Rect(
- displayWidth - boundsWidth,
- overlayOffsets.sensorLocationY,
- displayWidth,
- overlayOffsets.sensorLocationY + boundsHeight
- )
- } else {
- Rect(
- overlayOffsets.sensorLocationX,
- 0,
- overlayOffsets.sensorLocationX + boundsWidth,
- boundsHeight
- )
- }
-
- RotationUtils.rotateBounds(
- sensorBounds,
- Rect(0, 0, displayWidth, displayHeight),
- getRotationFromDefault(display.rotation)
- )
-
- overlayViewParams.x = sensorBounds.left
- overlayViewParams.y = sensorBounds.top
-
- windowManager.updateViewLayout(overlayView, overlayViewParams)
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
}
private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -373,89 +276,12 @@ private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Bo
private fun ActivityTaskManager.topClass(): String =
getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
- when (rotationFromDefault) {
- Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
- }
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean, rotationFromDefault: Int): Float =
- when (rotationFromDefault) {
- Surface.ROTATION_90 -> if (yAligned) 0f else 180f
- Surface.ROTATION_180 -> 180f
- Surface.ROTATION_270 -> if (yAligned) 180f else 0f
- else -> 0f
- }
-
private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
private fun Display.isNaturalOrientation(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-private fun LottieAnimationView.addOverlayDynamicColor(
- context: Context,
- @BiometricOverlayConstants.ShowReason reason: Int
-) {
- fun update() {
- val isKeyguard = reason == REASON_AUTH_KEYGUARD
- if (isKeyguard) {
- val color =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixed
- )
- val outerRimColor =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixedDim
- )
- val chevronFill =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorOnPrimaryFixed
- )
- addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
- }
- } else {
- if (!isDarkMode(context)) {
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
- }
- }
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(
- context.getColor(R.color.settingslib_color_blue400),
- PorterDuff.Mode.SRC_ATOP
- )
- }
- }
- }
- }
-
- if (composition != null) {
- update()
- } else {
- addLottieOnCompositionLoadedListener { update() }
- }
-}
-
-private fun isDarkMode(context: Context): Boolean {
- val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- return darkMode == Configuration.UI_MODE_NIGHT_YES
-}
-
-@VisibleForTesting
-class OrientationReasonListener(
+public class OrientationReasonListener(
context: Context,
displayManager: DisplayManager,
handler: Handler,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index c43722f2a0bb..efbde4c5985b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,8 +16,11 @@
package com.android.systemui.biometrics.data.repository
+import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -30,10 +33,8 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
/**
@@ -43,22 +44,17 @@ import kotlinx.coroutines.flow.shareIn
*/
interface FingerprintPropertyRepository {
- /**
- * If the repository is initialized or not. Other properties are defaults until this is true.
- */
- val isInitialized: Flow<Boolean>
-
/** The id of fingerprint sensor. */
- val sensorId: StateFlow<Int>
+ val sensorId: Flow<Int>
/** The security strength of sensor (convenience, weak, strong). */
- val strength: StateFlow<SensorStrength>
+ val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+ val sensorLocations: Flow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -66,10 +62,10 @@ class FingerprintPropertyRepositoryImpl
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val fingerprintManager: FingerprintManager
+ private val fingerprintManager: FingerprintManager?
) : FingerprintPropertyRepository {
- override val isInitialized: Flow<Boolean> =
+ private val props: Flow<FingerprintSensorPropertiesInternal> =
conflatedCallbackFlow {
val callback =
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -77,45 +73,47 @@ constructor(
sensors: List<FingerprintSensorPropertiesInternal>
) {
if (sensors.isNotEmpty()) {
- setProperties(sensors[0])
- trySendWithFailureLogging(true, TAG, "initialize properties")
+ trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
+ } else {
+ trySendWithFailureLogging(
+ DEFAULT_PROPS,
+ TAG,
+ "initialize with default properties"
+ )
}
}
}
- fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+ trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
awaitClose {}
}
.shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
- private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
-
- private val _strength: MutableStateFlow<SensorStrength> =
- MutableStateFlow(SensorStrength.CONVENIENCE)
- override val strength = _strength.asStateFlow()
-
- private val _sensorType: MutableStateFlow<FingerprintSensorType> =
- MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType = _sensorType.asStateFlow()
-
- private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
- MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
-
- private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
- _sensorId.value = prop.sensorId
- _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
- _sensorType.value = sensorTypeIntToObject(prop.sensorType)
- _sensorLocations.value =
- prop.allLocations.associateBy { sensorLocationInternal ->
+ override val sensorId: Flow<Int> = props.map { it.sensorId }
+ override val strength: Flow<SensorStrength> =
+ props.map { sensorStrengthIntToObject(it.sensorStrength) }
+ override val sensorType: Flow<FingerprintSensorType> =
+ props.map { sensorTypeIntToObject(it.sensorType) }
+ override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+ props.map {
+ it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
}
- }
+ }
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3b21a..37f39cb5fe0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,16 +17,24 @@
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
+ /** The displayId of the current display. */
+ val displayId: Flow<String>
- /** Get the corresponding offsets based on different displayId. */
- fun getOverlayOffsets(displayId: String): SensorLocationInternal
+ /** The corresponding offsets based on different displayId. */
+ val overlayOffsets: Flow<SensorLocationInternal>
+
+ /** Update the displayId. */
+ fun changeDisplay(displayId: String?)
}
@SysUISingleton
@@ -35,14 +43,16 @@ class SideFpsOverlayInteractorImpl
constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
SideFpsOverlayInteractor {
- override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
- val offsets = fingerprintPropertyRepository.sensorLocations.value
- return if (offsets.containsKey(displayId)) {
- offsets[displayId]!!
- } else {
- Log.w(TAG, "No location specified for display: $displayId")
- offsets[""]!!
+ private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+ override val displayId: Flow<String> = _displayId.asStateFlow()
+
+ override val overlayOffsets: Flow<SensorLocationInternal> =
+ combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+ offsets[displayId] ?: SensorLocationInternal.DEFAULT
}
+
+ override fun changeDisplay(displayId: String?) {
+ _displayId.value = displayId ?: ""
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
new file mode 100644
index 000000000000..0409519c9816
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.view.View
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for SideFpsOverlayView. */
+object SideFpsOverlayViewBinder {
+
+ /** Bind the view. */
+ @JvmStatic
+ fun bind(
+ view: View,
+ viewModel: SideFpsOverlayViewModel,
+ overlayViewParams: WindowManager.LayoutParams,
+ @BiometricOverlayConstants.ShowReason reason: Int,
+ @Application context: Context
+ ) {
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+ val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+
+ viewModel.changeDisplay()
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.sideFpsAnimationRotation.collect { rotation ->
+ view.rotation = rotation
+ }
+ }
+
+ launch {
+ // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
+ // in order to add scuba tests in the future.
+ viewModel.sideFpsAnimation.collect { animation ->
+ lottie.setAnimation(animation)
+ }
+ }
+
+ launch {
+ viewModel.sensorBounds.collect { sensorBounds ->
+ overlayViewParams.x = sensorBounds.left
+ overlayViewParams.y = sensorBounds.top
+
+ windowManager.updateViewLayout(view, overlayViewParams)
+ }
+ }
+
+ launch {
+ viewModel.overlayOffsets.collect { overlayOffsets ->
+ lottie.addLottieOnCompositionLoadedListener {
+ viewModel.updateSensorBounds(
+ it.bounds,
+ windowManager.maximumWindowMetrics.bounds,
+ overlayOffsets
+ )
+ }
+ }
+ }
+ }
+ }
+
+ lottie.addOverlayDynamicColor(context, reason)
+
+ /**
+ * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+ * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+ * in focus
+ */
+ view.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun dispatchPopulateAccessibilityEvent(
+ host: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ return if (
+ event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ ) {
+ true
+ } else {
+ super.dispatchPopulateAccessibilityEvent(host, event)
+ }
+ }
+ }
+ }
+}
+
+private fun LottieAnimationView.addOverlayDynamicColor(
+ context: Context,
+ @BiometricOverlayConstants.ShowReason reason: Int
+) {
+ fun update() {
+ val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+ if (isKeyguard) {
+ val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+ val chevronFill =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ android.R.attr.textColorPrimaryInverse
+ )
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (!isDarkMode(context)) {
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (isDarkMode(context)) {
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(
+ context.getColor(R.color.settingslib_color_blue400),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ }
+ }
+ }
+ }
+
+ if (composition != null) {
+ update()
+ } else {
+ addLottieOnCompositionLoadedListener { update() }
+ }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+ val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
new file mode 100644
index 000000000000..e938b4efb68c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.util.RotationUtils
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.annotation.RawRes
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View-model for SideFpsOverlayView. */
+class SideFpsOverlayViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val sideFpsOverlayInteractor: SideFpsOverlayInteractor,
+) {
+
+ private val isReverseDefaultRotation =
+ context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+
+ private val _sensorBounds: MutableStateFlow<Rect> = MutableStateFlow(Rect())
+ val sensorBounds = _sensorBounds.asStateFlow()
+
+ val overlayOffsets: Flow<SensorLocationInternal> = sideFpsOverlayInteractor.overlayOffsets
+
+ /** Update the displayId. */
+ fun changeDisplay() {
+ sideFpsOverlayInteractor.changeDisplay(context.display!!.uniqueId)
+ }
+
+ /** Determine the rotation of the sideFps animation given the overlay offsets. */
+ val sideFpsAnimationRotation: Flow<Float> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+ Surface.ROTATION_180 -> 180f
+ Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+ else -> 0f
+ }
+ }
+
+ /** Populate the sideFps animation from the overlay offsets. */
+ @RawRes
+ val sideFpsAnimation: Flow<Int> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ Surface.ROTATION_180 ->
+ if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+ }
+ }
+
+ /**
+ * Calculate and update the bounds of the sensor based on the bounds of the overlay view, the
+ * maximum bounds of the window, and the offsets of the sensor location.
+ */
+ fun updateSensorBounds(
+ bounds: Rect,
+ maximumWindowBounds: Rect,
+ offsets: SensorLocationInternal
+ ) {
+ val isNaturalOrientation = context.display!!.isNaturalOrientation()
+ val isDefaultOrientation =
+ if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
+
+ val displayWidth =
+ if (isDefaultOrientation) maximumWindowBounds.width() else maximumWindowBounds.height()
+ val displayHeight =
+ if (isDefaultOrientation) maximumWindowBounds.height() else maximumWindowBounds.width()
+ val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
+ val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
+ val sensorBounds =
+ if (offsets.isYAligned()) {
+ Rect(
+ displayWidth - boundsWidth,
+ offsets.sensorLocationY,
+ displayWidth,
+ offsets.sensorLocationY + boundsHeight
+ )
+ } else {
+ Rect(
+ offsets.sensorLocationX,
+ 0,
+ offsets.sensorLocationX + boundsWidth,
+ boundsHeight
+ )
+ }
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ Rect(0, 0, displayWidth, displayHeight),
+ getRotationFromDefault(displayInfo.rotation)
+ )
+
+ _sensorBounds.value = sensorBounds
+ }
+
+ private fun getRotationFromDefault(rotation: Int): Int =
+ if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+}
+
+private fun Display.isNaturalOrientation(): Boolean =
+ rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 2abdb849cd9a..e3e9b3a3754a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,10 +16,10 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -35,8 +35,8 @@ constructor(
private val keyguardStateController: KeyguardStateController,
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricSettingsRepository: BiometricSettingsRepository,
- private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
@@ -78,7 +78,7 @@ constructor(
biometricSettingsRepository.isFingerprintEnrolled.value &&
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
- !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+ !keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
!statusBarStateController.isDozing
}
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 62a484d42dec..8e14237c0586 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
@@ -88,7 +88,7 @@ constructor(
/** Whether the auto confirm feature is enabled for the currently-selected user. */
val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
- /** The length of the PIN for which we should show a hint. */
+ /** The length of the hinted PIN, or `null`, if pin length hint should not be shown. */
val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
/** Whether the pattern should be visible for the currently-selected user. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f68bd49230d9..35cf4a1ecf0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerIm
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallpapers.dagger.WallpaperModule;
import dagger.Binds;
import dagger.Module;
@@ -106,6 +107,7 @@ import javax.inject.Named;
StatusBarEventsModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class,
+ WallpaperModule.class,
KeyboardShortcutsModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index b1f513d0945c..a560accfff68 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -51,6 +51,7 @@ import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.statusbar.phone.LockscreenWallpaper
+import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -59,6 +60,7 @@ import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
import dagger.Module
@@ -72,6 +74,7 @@ import dagger.multibindings.IntoMap
MultiUserUtilsModule::class,
StartControlsStartableModule::class,
StartBinderLoggerModule::class,
+ WallpaperModule::class,
])
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@@ -316,4 +319,9 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(LockscreenWallpaper::class)
abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(ScrimController::class)
+ abstract fun bindScrimController(impl: ScrimController): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 83c1c7100761..0670ec380861 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,10 +61,6 @@ object Flags {
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
- // TODO(b/279735475): Tracking Bug
- @JvmField
- val NEW_LIGHT_BAR_LOGIC = releasedFlag(279735475, "new_light_bar_logic")
-
/**
* This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
* enable it on release builds.
@@ -72,9 +68,6 @@ object Flags {
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
unreleasedFlag(119, "notification_memory_logging_enabled")
- // TODO(b/257315550): Tracking Bug
- val NO_HUN_FOR_OLD_WHEN = releasedFlag(118, "no_hun_for_old_when")
-
// TODO(b/260335638): Tracking Bug
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION =
@@ -445,10 +438,6 @@ object Flags {
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
- // TODO(b/266739309): Tracking Bug
- @JvmField
- val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
-
// TODO(b/267007629): Tracking Bug
val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index f59ad90d86ff..23f6fa6bbf23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -31,6 +31,9 @@ import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommand
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -40,7 +43,9 @@ class KeyguardViewConfigurator
@Inject
constructor(
private val keyguardRootView: KeyguardRootView,
+ private val sharedNotificationContainer: SharedNotificationContainer,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
@@ -55,10 +60,28 @@ constructor(
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
bindIndicationArea(notificationPanel)
bindLockIconView(notificationPanel)
+ setupNotificationStackScrollLayout(notificationPanel)
+
keyguardLayoutManager.layoutViews()
keyguardLayoutManagerCommandListener.start()
}
+ fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ // This moves the existing NSSL view to a different parent, as the controller is a
+ // singleton and recreating it has other bad side effects
+ val nssl =
+ legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
+ (it.getParent() as ViewGroup).removeView(it)
+ }
+ sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel
+ )
+ }
+ }
+
fun bindIndicationArea(legacyParent: ViewGroup) {
indicationAreaHandle?.dispose()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 468d7606933e..86cd962719e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -163,9 +163,11 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -290,6 +292,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
private final DreamOverlayStateController mDreamOverlayStateController;
+ private final JavaAdapter mJavaAdapter;
+ private final WallpaperRepository mWallpaperRepository;
/** The stream type that the lock sounds are tied to. */
private int mUiSoundsStreamType;
@@ -1322,6 +1326,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -1382,6 +1388,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
mDreamOverlayStateController = dreamOverlayStateController;
+ mJavaAdapter = javaAdapter;
+ mWallpaperRepository = wallpaperRepository;
mActivityLaunchAnimator = activityLaunchAnimator;
mScrimControllerLazy = scrimControllerLazy;
@@ -1484,6 +1492,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+ this::setWallpaperSupportsAmbientMode);
}
// TODO(b/273443374) remove, temporary util to get a feature flag
@@ -3458,7 +3470,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
* with the light reveal scrim.
*/
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
mWallpaperSupportsAmbientMode = supportsAmbientMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 29a2d12ca972..4205ed22ff24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -66,9 +66,11 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -130,6 +132,8 @@ public class KeyguardModule {
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -170,6 +174,8 @@ public class KeyguardModule {
keyguardTransitions,
interactionJankMonitor,
dreamOverlayStateController,
+ javaAdapter,
+ wallpaperRepository,
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index a3d1abedcc2c..6edf40f47521 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -38,13 +38,13 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
@@ -88,10 +88,10 @@ interface DeviceEntryFaceAuthRepository {
val canRunFaceAuth: StateFlow<Boolean>
/** Provide the current status of face authentication. */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Provide the current status of face detection. */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Current state of whether face authentication is locked out or not. */
val isLockedOut: StateFlow<Boolean>
@@ -151,13 +151,13 @@ constructor(
private var cancelNotReceivedHandlerJob: Job? = null
private var halErrorRetryJob: Job? = null
- private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+ private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
MutableStateFlow(null)
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = _authenticationStatus.filterNotNull()
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
private val _isLockedOut = MutableStateFlow(false)
@@ -396,18 +396,18 @@ constructor(
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedAuthenticationStatus
+ _authenticationStatus.value = FailedFaceAuthenticationStatus
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
onFaceAuthRequestCompleted()
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
- _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+ _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
- val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+ val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
if (errorStatus.isLockoutError()) {
_isLockedOut.value = true
}
@@ -433,11 +433,11 @@ constructor(
if (faceAcquiredInfoIgnoreList.contains(code)) {
return
}
- _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+ _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
}
override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
- _authenticationStatus.value = SuccessAuthenticationStatus(result)
+ _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
_isAuthenticated.value = true
faceAuthLogger.faceAuthSuccess(result)
onFaceAuthRequestCompleted()
@@ -482,7 +482,7 @@ constructor(
private val detectionCallback =
FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
faceAuthLogger.faceDetected()
- _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+ _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
}
private var cancellationInProgress = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 52234b32b83a..9bec30052476 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -21,27 +21,29 @@ import android.hardware.biometrics.BiometricAuthenticator.Modality
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
- val isLockedOut: StateFlow<Boolean>
+ val isLockedOut: Flow<Boolean>
/**
* Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -53,6 +55,9 @@ interface DeviceEntryFingerprintAuthRepository {
* Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
*/
val availableFpSensorType: Flow<BiometricType?>
+
+ /** Provide the current status of fingerprint authentication. */
+ val authenticationStatus: Flow<FingerprintAuthenticationStatus>
}
/**
@@ -69,16 +74,7 @@ constructor(
val authController: AuthController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Application scope: CoroutineScope,
- dumpManager: DumpManager,
-) : DeviceEntryFingerprintAuthRepository, Dumpable {
-
- init {
- dumpManager.registerDumpable(this)
- }
-
- override fun dump(pw: PrintWriter, args: Array<String?>) {
- pw.println("isLockedOut=${isLockedOut.value}")
- }
+) : DeviceEntryFingerprintAuthRepository {
override val availableFpSensorType: Flow<BiometricType?>
get() {
@@ -114,7 +110,7 @@ constructor(
else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
}
- override val isLockedOut: StateFlow<Boolean> =
+ override val isLockedOut: Flow<Boolean> =
conflatedCallbackFlow {
val sendLockoutUpdate =
fun() {
@@ -138,7 +134,7 @@ constructor(
sendLockoutUpdate()
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
override val isRunning: Flow<Boolean>
get() = conflatedCallbackFlow {
@@ -166,6 +162,93 @@ constructor(
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean,
+ ) {
+
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ SuccessFingerprintAuthenticationStatus(
+ userId,
+ isStrongBiometric,
+ ),
+ )
+ }
+
+ override fun onBiometricError(
+ msgId: Int,
+ errString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ ErrorFingerprintAuthenticationStatus(
+ msgId,
+ errString,
+ ),
+ )
+ }
+
+ override fun onBiometricHelp(
+ msgId: Int,
+ helpString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ HelpFingerprintAuthenticationStatus(
+ msgId,
+ helpString,
+ ),
+ )
+ }
+
+ override fun onBiometricAuthFailed(
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ FailFingerprintAuthenticationStatus,
+ )
+ }
+
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType,
+ acquireInfo: Int,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ AcquiredFingerprintAuthenticationStatus(
+ acquireInfo,
+ ),
+ )
+ }
+
+ private fun sendUpdateIfFingerprint(
+ biometricSourceType: BiometricSourceType,
+ authenticationStatus: FingerprintAuthenticationStatus
+ ) {
+ if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+ return
+ }
+
+ trySendWithFailureLogging(
+ authenticationStatus,
+ TAG,
+ "new fingerprint authentication status"
+ )
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
}
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 d119920e1a42..7475c4251211 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
@@ -31,26 +31,31 @@ import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+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.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 javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -147,6 +152,9 @@ interface KeyguardRepository {
/** Observable for device wake/sleep state */
val wakefulness: StateFlow<WakefulnessModel>
+ /** Observable for device screen state */
+ val screenModel: StateFlow<ScreenModel>
+
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -162,6 +170,9 @@ interface KeyguardRepository {
/** Whether quick settings or quick-quick settings is visible. */
val isQuickSettingsVisible: Flow<Boolean>
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -171,6 +182,14 @@ 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)
@@ -195,6 +214,8 @@ interface KeyguardRepository {
fun setIsDozing(isDozing: Boolean)
fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
+
+ fun dozeTimeTick()
}
/** Encapsulates application state for the keyguard. */
@@ -204,8 +225,10 @@ class KeyguardRepositoryImpl
constructor(
statusBarStateController: StatusBarStateController,
wakefulnessLifecycle: WakefulnessLifecycle,
+ screenLifecycle: ScreenLifecycle,
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
+ private val keyguardBypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val dozeParameters: DozeParameters,
@@ -252,23 +275,17 @@ constructor(
override val isAodAvailable: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
- object : DozeParameters.Callback {
- override fun onAlwaysOnChange() {
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "updated isAodAvailable"
- )
- }
+ DozeParameters.Callback {
+ trySendWithFailureLogging(
+ dozeParameters.alwaysOn,
+ TAG,
+ "updated isAodAvailable"
+ )
}
dozeParameters.addCallback(callback)
// Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "initial isAodAvailable"
- )
+ trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
awaitClose { dozeParameters.removeCallback(callback) }
}
@@ -366,6 +383,13 @@ constructor(
_isDozing.value = isDozing
}
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -464,6 +488,10 @@ constructor(
return keyguardStateController.isShowing
}
+ override fun isBypassEnabled(): Boolean {
+ return keyguardBypassController.bypassEnabled
+ }
+
override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
@@ -551,6 +579,42 @@ constructor(
initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
)
+ override val screenModel: StateFlow<ScreenModel> =
+ conflatedCallbackFlow {
+ val observer =
+ object : ScreenLifecycle.Observer {
+ override fun onScreenTurningOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurningOff() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOff() {
+ dispatchNewState()
+ }
+
+ private fun dispatchNewState() {
+ trySendWithFailureLogging(
+ ScreenModel.fromScreenLifecycle(screenLifecycle),
+ TAG,
+ "updated screen state",
+ )
+ }
+ }
+
+ screenLifecycle.addObserver(observer)
+ awaitClose { screenLifecycle.removeObserver(observer) }
+ }
+ .stateIn(
+ scope,
+ // Use Eagerly so that we're always listening and never miss an event.
+ SharingStarted.Eagerly,
+ initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle),
+ )
+
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendFpLocation() {
trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index abe59b76816f..27e3a749a6c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -18,8 +18,8 @@ package com.android.systemui.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,10 +40,10 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA
private val _canRunFaceAuth = MutableStateFlow(false)
override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
private val _isLockedOut = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
new file mode 100644
index 000000000000..c849b8495a26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
+ * authentication events that should never surface a message to the user at the current device
+ * state.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BiometricMessageInteractor
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+ private val indicationHelper: IndicationHelper,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+ val fingerprintErrorMessage: Flow<BiometricMessage> =
+ fingerprintAuthRepository.authenticationStatus
+ .filter {
+ it is ErrorFingerprintAuthenticationStatus &&
+ !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
+ }
+ .map {
+ val errorStatus = it as ErrorFingerprintAuthenticationStatus
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.ERROR,
+ errorStatus.msgId,
+ errorStatus.msg,
+ )
+ }
+
+ val fingerprintHelpMessage: Flow<BiometricMessage> =
+ fingerprintAuthRepository.authenticationStatus
+ .filter { it is HelpFingerprintAuthenticationStatus }
+ .filterNot { isPrimaryAuthRequired() }
+ .map {
+ val helpStatus = it as HelpFingerprintAuthenticationStatus
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.HELP,
+ helpStatus.msgId,
+ helpStatus.msg,
+ )
+ }
+
+ val fingerprintFailMessage: Flow<BiometricMessage> =
+ isUdfps().flatMapLatest { isUdfps ->
+ fingerprintAuthRepository.authenticationStatus
+ .filter { it is FailFingerprintAuthenticationStatus }
+ .filterNot { isPrimaryAuthRequired() }
+ .map {
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.FAIL,
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ if (isUdfps) {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ } else {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ },
+ )
+ }
+ }
+
+ private fun isUdfps() =
+ fingerprintPropertyRepository.sensorType.map {
+ it == FingerprintSensorType.UDFPS_OPTICAL ||
+ it == FingerprintSensorType.UDFPS_ULTRASONIC
+ }
+
+ private fun isPrimaryAuthRequired(): Boolean {
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed since strong biometrics can still be
+ // used.
+ return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+ }
+}
+
+data class BiometricMessage(
+ val source: BiometricSourceType,
+ val type: BiometricMessageType,
+ val id: Int,
+ val message: String?,
+)
+
+enum class BiometricMessageType {
+ HELP,
+ ERROR,
+ FAIL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 2efcd0c1ffe7..0c898befe6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -35,4 +35,8 @@ constructor(
fun setLastTapToWakePosition(position: Point) {
keyguardRepository.setLastDozeTapToWakePosition(position)
}
+
+ fun dozeTimeTick() {
+ keyguardRepository.dozeTimeTick()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 74ef7a50fd44..141b13055889 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -16,8 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
/**
@@ -27,10 +27,10 @@ import kotlinx.coroutines.flow.Flow
interface KeyguardFaceAuthInteractor {
/** Current authentication status */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Current detection status */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
@@ -72,8 +72,8 @@ interface KeyguardFaceAuthInteractor {
*/
interface FaceAuthenticationListener {
/** Receive face authentication status updates */
- fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
/** Receive status updates whenever face detection runs */
- fun onDetectionStatusChanged(status: DetectionStatus)
+ fun onDetectionStatusChanged(status: FaceDetectionStatus)
}
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 7fae7522d981..1553525915d5 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
@@ -70,6 +70,8 @@ constructor(
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
/** Whether Always-on Display mode is available. */
val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 5005b6c7f0df..10dd900e437e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -31,9 +31,9 @@ import kotlinx.coroutines.flow.emptyFlow
*/
@SysUISingleton
class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index d467225a9d63..6e7a20b092f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -30,8 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.util.kotlin.pairwise
@@ -165,10 +165,10 @@ constructor(
repository.cancel()
}
- private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
+ private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
/** Provide the status of face authentication */
override val authenticationStatus =
- merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+ merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
@@ -176,13 +176,13 @@ constructor(
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
if (repository.isLockedOut.value) {
- _authenticationStatusOverride.value =
- ErrorAuthenticationStatus(
+ faceAuthenticationStatusOverride.value =
+ ErrorFaceAuthenticationStatus(
BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
context.resources.getString(R.string.keyguard_face_unlock_unavailable)
)
} else {
- _authenticationStatusOverride.value = null
+ faceAuthenticationStatusOverride.value = null
applicationScope.launch {
faceAuthenticationLogger.authRequested(uiEvent)
repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index b354cfd27687..0f6d82ed4b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -23,33 +23,35 @@ import android.os.SystemClock.elapsedRealtime
* Authentication status provided by
* [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
*/
-sealed class AuthenticationStatus
+sealed class FaceAuthenticationStatus
/** Success authentication status. */
-data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
- AuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
+ FaceAuthenticationStatus()
/** Face authentication help message. */
-data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) :
+ FaceAuthenticationStatus()
/** Face acquired message. */
-data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus()
/** Face authentication failed message. */
-object FailedAuthenticationStatus : AuthenticationStatus()
+object FailedFaceAuthenticationStatus : FaceAuthenticationStatus()
/** Face authentication error message */
-data class ErrorAuthenticationStatus(
+data class ErrorFaceAuthenticationStatus(
val msgId: Int,
val msg: String? = null,
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime()
-) : AuthenticationStatus() {
+) : FaceAuthenticationStatus() {
/**
* Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out.
*/
- fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+ fun isLockoutError() =
+ msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT
/**
* Method that checks if [msgId] is a cancellation error. This means that face authentication
@@ -64,4 +66,4 @@ data class ErrorAuthenticationStatus(
}
/** Face detection success message. */
-data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
new file mode 100644
index 000000000000..7fc6016bf087
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.keyguard.shared.model
+
+import android.os.SystemClock.elapsedRealtime
+
+/**
+ * Fingerprint authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ */
+sealed class FingerprintAuthenticationStatus
+
+/** Fingerprint authentication success status. */
+data class SuccessFingerprintAuthenticationStatus(
+ val userId: Int,
+ val isStrongBiometric: Boolean,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication help message. */
+data class HelpFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String?,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint acquired message. */
+data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
+ FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication failed message. */
+object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication error message */
+data class ErrorFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String? = null,
+ // present to break equality check if the same error occurs repeatedly.
+ val createdAt: Long = elapsedRealtime(),
+) : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
new file mode 100644
index 000000000000..80a1b75c4350
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+/** Model device screen lifecycle states. */
+data class ScreenModel(
+ val state: ScreenState,
+) {
+ companion object {
+ fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel {
+ return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState()))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
new file mode 100644
index 000000000000..fe5d9355a6fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+enum class ScreenState {
+ /** Screen is fully off. */
+ SCREEN_OFF,
+ /** Signal that the screen is turning on. */
+ SCREEN_TURNING_ON,
+ /** Screen is fully on. */
+ SCREEN_ON,
+ /** Signal that the screen is turning off. */
+ SCREEN_TURNING_OFF;
+
+ companion object {
+ fun fromScreenLifecycleInt(value: Int): ScreenState {
+ return when (value) {
+ ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF
+ ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON
+ ScreenLifecycle.SCREEN_ON -> SCREEN_ON
+ ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF
+ else -> throw IllegalArgumentException("Invalid screen value: $value")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
index 728dd3911663..9872d97021fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -37,6 +37,7 @@ object UdfpsAodFingerprintViewBinder {
view: LottieAnimationView,
viewModel: UdfpsAodViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
index 26ef4685d286..0113628c30ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -38,6 +38,7 @@ object UdfpsBackgroundViewBinder {
view: ImageView,
viewModel: BackgroundViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
index 0ab8e52fb6c7..bab04f234b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -42,6 +42,7 @@ object UdfpsFingerprintViewBinder {
view: LottieAnimationView,
viewModel: FingerprintViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 68cdfb6d5865..373f70582612 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,7 +4,7 @@ import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.FaceAuthLog
@@ -240,7 +240,7 @@ constructor(
)
}
- fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+ fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) {
logBuffer.log(
TAG,
DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 0b33904829d5..258284e8af51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -31,15 +31,12 @@ import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
+class RecommendationViewHolder private constructor(itemView: View) {
val recommendations = itemView as TransitionLayout
// Recommendation screen
- lateinit var cardIcon: ImageView
- lateinit var mediaAppIcons: List<CachingIconView>
- lateinit var mediaProgressBars: List<SeekBar>
- lateinit var cardTitle: TextView
+ val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
val mediaCoverContainers =
listOf<ViewGroup>(
@@ -47,53 +44,25 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView:
itemView.requireViewById(R.id.media_cover2_container),
itemView.requireViewById(R.id.media_cover3_container)
)
+ val mediaAppIcons: List<CachingIconView> =
+ mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
val mediaTitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
val mediaSubtitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+ val mediaProgressBars: List<SeekBar> =
+ mediaCoverContainers.map {
+ it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+ // Media playback is in the direction of tape, not time, so it stays LTR
+ layoutDirection = View.LAYOUT_DIRECTION_LTR
+ }
}
val mediaCoverItems: List<ImageView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
val gutsViewHolder = GutsViewHolder(itemView)
init {
- if (updatedView) {
- mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
- cardTitle = itemView.requireViewById(R.id.media_rec_title)
- mediaProgressBars =
- mediaCoverContainers.map {
- it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
- // Media playback is in the direction of tape, not time, so it stays LTR
- layoutDirection = View.LAYOUT_DIRECTION_LTR
- }
- }
- } else {
- cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- }
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(gutsViewHolder.cancel)
@@ -114,63 +83,31 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView:
* @param parent Parent of inflated view.
*/
@JvmStatic
- fun create(
- inflater: LayoutInflater,
- parent: ViewGroup,
- updatedView: Boolean,
- ): RecommendationViewHolder {
+ fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
val itemView =
- if (updatedView) {
- inflater.inflate(
- R.layout.media_recommendations,
- parent,
- false /* attachToRoot */
- )
- } else {
- inflater.inflate(
- R.layout.media_smartspace_recommendations,
- parent,
- false /* attachToRoot */
- )
- }
+ inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView, updatedView)
+ return RecommendationViewHolder(itemView)
}
// Res Ids for the control components on the recommendation view.
val controlsIds =
setOf(
- R.id.recommendation_card_icon,
R.id.media_rec_title,
- R.id.media_cover1,
- R.id.media_cover2,
- R.id.media_cover3,
R.id.media_cover,
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
val mediaTitlesAndSubtitlesIds =
setOf(
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 70b5e75e6048..398dcf260dff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -744,11 +744,7 @@ constructor(
val newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(
- LayoutInflater.from(context),
- mediaContent,
- mediaFlags.isRecommendationCardUpdateEnabled()
- )
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a978b92cb234..a12bc2c99d63 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -784,14 +784,7 @@ public class MediaControlPanel {
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
} else if (data != null) {
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_header);
- } else {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_description,
- data.getAppName(mContext));
- }
+ contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
} else {
contentDescription = null;
}
@@ -1377,10 +1370,6 @@ public class MediaControlPanel {
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
- headerLogoImageView.setImageDrawable(icon);
- }
fetchAndUpdateRecommendationColors(icon);
// Set up media rec card's tap action if applicable.
@@ -1401,16 +1390,7 @@ public class MediaControlPanel {
// Set up media item cover.
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- bindRecommendationArtwork(
- recommendation,
- data.getPackageName(),
- itemIndex
- );
- } else {
- mediaCoverImageView.post(
- () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
- }
+ bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1455,21 +1435,18 @@ public class MediaControlPanel {
subtitleView.setText(subtitle);
// Set up progress bar
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- SeekBar mediaProgressBar =
- mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
- TextView mediaSubtitle =
- mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- // show progress bar if the recommended album is played.
- Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
- if (progress == null || progress <= 0.0) {
- mediaProgressBar.setVisibility(View.GONE);
- mediaSubtitle.setVisibility(View.VISIBLE);
- } else {
- mediaProgressBar.setProgress((int) (progress * 100));
- mediaProgressBar.setVisibility(View.VISIBLE);
- mediaSubtitle.setVisibility(View.GONE);
- }
+ SeekBar mediaProgressBar =
+ mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+ TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+ // show progress bar if the recommended album is played.
+ Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+ if (progress == null || progress <= 0.0) {
+ mediaProgressBar.setVisibility(View.GONE);
+ mediaSubtitle.setVisibility(View.VISIBLE);
+ } else {
+ mediaProgressBar.setProgress((int) (progress * 100));
+ mediaProgressBar.setVisibility(View.VISIBLE);
+ mediaSubtitle.setVisibility(View.GONE);
}
}
mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1588,9 +1565,7 @@ public class MediaControlPanel {
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
- }
+ mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
@@ -1598,12 +1573,9 @@ public class MediaControlPanel {
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getMediaProgressBars().forEach(
- (progressBar) -> progressBar.setProgressTintList(
- ColorStateList.valueOf(textPrimaryColor))
- );
- }
+ mRecommendationViewHolder.getMediaProgressBars().forEach(
+ (progressBar) -> progressBar.setProgressTintList(
+ ColorStateList.valueOf(textPrimaryColor)));
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bca778b77c5..1dd969f9bea5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -655,13 +655,8 @@ constructor(
expandedLayout.load(context, R.xml.media_session_expanded)
}
TYPE.RECOMMENDATION -> {
- if (mediaFlags.isRecommendationCardUpdateEnabled()) {
- collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
- expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
- } else {
- collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
- expandedLayout.load(context, R.xml.media_recommendation_expanded)
- }
+ collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 01f047ccd4f5..09aef8826200 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -49,10 +49,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
*/
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
- /** Check whether we show the updated recommendation card. */
- fun isRecommendationCardUpdateEnabled() =
- featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
-
/** Check whether to get progress information for resume players */
fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index eb1ca66f6ca8..809edc09070a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -70,6 +70,19 @@ constructor(
}
}
+ /**
+ * Wakes up the device if dreaming with a screensaver.
+ *
+ * @param why a string explaining why we're waking the device for debugging purposes. Should be
+ * in SCREAMING_SNAKE_CASE.
+ * @param wakeReason the PowerManager-based reason why we're waking the device.
+ */
+ fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
+ if (statusBarStateController.isDreaming) {
+ repository.wakeUp(why, wakeReason)
+ }
+ }
+
companion object {
private const val FSI_WAKE_WHY = "full_screen_intent"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 83b373d5e626..856a92e85ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -58,6 +58,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private boolean mListening;
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
@@ -159,12 +160,15 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
public void setListening(boolean listening, boolean expanded) {
setListening(listening && expanded);
- // Set the listening as soon as the QS fragment starts listening regardless of the
- //expansion, so it will update the current brightness before the slider is visible.
- if (listening) {
- mBrightnessController.registerCallbacks();
- } else {
- mBrightnessController.unregisterCallbacks();
+ if (listening != mListening) {
+ mListening = listening;
+ // Set the listening as soon as the QS fragment starts listening regardless of the
+ //expansion, so it will update the current brightness before the slider is visible.
+ if (listening) {
+ mBrightnessController.registerCallbacks();
+ } else {
+ mBrightnessController.unregisterCallbacks();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index d81e4c229aa7..764ef681106b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -116,6 +116,10 @@ open class QSTileViewImpl @JvmOverloads constructor(
private lateinit var customDrawableView: ImageView
private lateinit var chevronView: ImageView
private var mQsLogger: QSLogger? = null
+
+ /**
+ * Controls if tile background is set to a [RippleDrawable] see [setClickable]
+ */
protected var showRippleEffect = true
private lateinit var ripple: RippleDrawable
@@ -440,7 +444,6 @@ open class QSTileViewImpl @JvmOverloads constructor(
protected open fun handleStateChanged(state: QSTile.State) {
val allowAnimations = animationsEnabled()
- showRippleEffect = state.showRippleEffect
isClickable = state.state != Tile.STATE_UNAVAILABLE
isLongClickable = state.handlesLongClick
icon.setIcon(state, allowAnimations)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index a444e7631527..9f10e6f1f2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -155,7 +155,6 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
state.contentDescription = state.label;
state.value = mPowerSave;
state.expandedAccessibilityClassName = Switch.class.getName();
- state.showRippleEffect = mSetting.getValue() == 0;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index a60d1ad448b4..9ffcba62ad09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -169,7 +169,6 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
? R.drawable.qs_light_dark_theme_icon_on
: R.drawable.qs_light_dark_theme_icon_off);
- state.showRippleEffect = false;
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
index 285ff74f8b2e..b23c4ec9aa8b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -77,7 +77,7 @@ constructor(
authenticationInteractor.isUnlocked
.map { isUnlocked ->
val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
- val isBypassEnabled = authenticationInteractor.isBypassEnabled.value
+ val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
when (currentSceneKey) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 5aa5feeedcf1..f9324a95c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -16,19 +16,25 @@
package com.android.systemui.scene.ui.view
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.time.Instant
import kotlinx.coroutines.launch
object SceneWindowRootViewBinder {
@@ -77,6 +83,9 @@ object SceneWindowRootViewBinder {
)
)
+ val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
+ view.addView(createVisibilityToggleView(legacyView))
+
launch {
viewModel.isVisible.collect { isVisible ->
onVisibilityChangedInternal(isVisible)
@@ -89,4 +98,28 @@ object SceneWindowRootViewBinder {
}
}
}
+
+ private var clickCount = 0
+ private var lastClick = Instant.now()
+
+ /**
+ * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
+ * tapping 5 times in quick succession on the device camera (top center).
+ */
+ // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
+ // SysUI altogether.
+ private fun createVisibilityToggleView(otherView: View): View {
+ val toggleView = View(otherView.context)
+ toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
+ toggleView.setOnClickListener {
+ val now = Instant.now()
+ clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
+ if (clickCount == 5) {
+ otherView.isVisible = !otherView.isVisible
+ clickCount = 0
+ }
+ lastClick = now
+ }
+ return toggleView
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 9594ba374fc3..6af9b739da52 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -22,7 +22,6 @@ import static com.android.settingslib.display.BrightnessUtils.convertLinearToGam
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
@@ -31,10 +30,10 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -43,6 +42,8 @@ import android.service.vr.IVrStateCallbacks;
import android.util.Log;
import android.util.MathUtils;
+import androidx.annotation.Nullable;
+
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,10 +53,13 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
@@ -75,8 +79,11 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
private final DisplayManager mDisplayManager;
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
+ @Nullable
private final IVrManager mVrManager;
+ private final SecureSettings mSecureSettings;
+
private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -106,6 +113,8 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
/** ContentObserver to watch brightness */
private class BrightnessObserver extends ContentObserver {
+ private boolean mObserving = false;
+
BrightnessObserver(Handler handler) {
super(handler);
}
@@ -124,19 +133,17 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
public void startObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- cr.registerContentObserver(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
- mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
- new HandlerExecutor(mHandler));
+ if (!mObserving) {
+ mObserving = true;
+ mSecureSettings.registerContentObserverForUser(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
public void stopObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- mDisplayTracker.removeCallback(mBrightnessListener);
+ mSecureSettings.unregisterContentObserver(this);
+ mObserving = false;
}
}
@@ -159,6 +166,8 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
mBrightnessObserver.startObserving();
+ mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+ new HandlerExecutor(mMainHandler));
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
@@ -166,7 +175,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
mUpdateModeRunnable.run();
mUpdateSliderRunnable.run();
- mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
}
};
@@ -187,9 +196,10 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
mBrightnessObserver.stopObserving();
+ mDisplayTracker.removeCallback(mBrightnessListener);
mUserTracker.removeCallback(mUserChangedCallback);
- mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
};
@@ -225,7 +235,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
mBrightnessMin = info.brightnessMinimum;
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(info.brightness);
- mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
+ mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
inVrMode ? 1 : 0).sendToTarget();
}
};
@@ -233,14 +243,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
- mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
+ mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
.sendToTarget();
}
};
- private final Handler mHandler = new Handler() {
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
- public void handleMessage(Message msg) {
+ public boolean handleMessage(Message msg) {
mExternalChange = true;
try {
switch (msg.what) {
@@ -257,14 +267,18 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
updateVrMode(msg.arg1 != 0);
break;
default:
- super.handleMessage(msg);
+ return false;
+
}
} finally {
mExternalChange = false;
}
+ return true;
}
};
+ private final Handler mMainHandler;
+
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
@@ -274,12 +288,17 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
}
};
+ @AssistedInject
public BrightnessController(
Context context,
- ToggleSlider control,
+ @Assisted ToggleSlider control,
UserTracker userTracker,
DisplayTracker displayTracker,
+ DisplayManager displayManager,
+ SecureSettings secureSettings,
+ @Nullable IVrManager iVrManager,
@Main Executor mainExecutor,
+ @Main Looper mainLooper,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
@@ -288,22 +307,23 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
mBackgroundHandler = bgHandler;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
- mBrightnessObserver = new BrightnessObserver(mHandler);
-
+ mSecureSettings = secureSettings;
mDisplayId = mContext.getDisplayId();
- PowerManager pm = context.getSystemService(PowerManager.class);
+ mDisplayManager = displayManager;
+ mVrManager = iVrManager;
- mDisplayManager = context.getSystemService(DisplayManager.class);
- mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
- Context.VR_SERVICE));
+ mMainHandler = new Handler(mainLooper, mHandlerCallback);
+ mBrightnessObserver = new BrightnessObserver(mMainHandler);
}
public void registerCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
mBackgroundHandler.post(mStartListeningRunnable);
}
/** Unregister all call backs, both to and from the controller */
public void unregisterCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
mBackgroundHandler.post(mStopListeningRunnable);
mControlValueInitialized = false;
}
@@ -418,38 +438,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
mSliderAnimator.start();
}
- /** Factory for creating a {@link BrightnessController}. */
- public static class Factory {
- private final Context mContext;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
- private final Executor mMainExecutor;
- private final Handler mBackgroundHandler;
-
- @Inject
- public Factory(
- Context context,
- UserTracker userTracker,
- DisplayTracker displayTracker,
- @Main Executor mainExecutor,
- @Background Handler bgHandler) {
- mContext = context;
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
- }
+
+ /** Factory for creating a {@link BrightnessController}. */
+ @AssistedFactory
+ public interface Factory {
/** Create a {@link BrightnessController} */
- public BrightnessController create(ToggleSlider toggleSlider) {
- return new BrightnessController(
- mContext,
- toggleSlider,
- mUserTracker,
- mDisplayTracker,
- mMainExecutor,
- mBackgroundHandler);
- }
+ BrightnessController create(ToggleSlider toggleSlider);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 182e4569b549..38b1f14e45de 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -23,7 +23,6 @@ import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KE
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -37,10 +36,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,26 +52,21 @@ public class BrightnessDialog extends Activity {
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
+ private final BrightnessController.Factory mBrightnessControllerFactory;
private final DelayableExecutor mMainExecutor;
- private final Handler mBackgroundHandler;
private final AccessibilityManagerWrapper mAccessibilityMgr;
private Runnable mCancelTimeoutRunnable;
@Inject
public BrightnessDialog(
- UserTracker userTracker,
- DisplayTracker displayTracker,
- BrightnessSliderController.Factory factory,
+ BrightnessSliderController.Factory brightnessSliderfactory,
+ BrightnessController.Factory brightnessControllerFactory,
@Main DelayableExecutor mainExecutor,
- @Background Handler bgHandler,
- AccessibilityManagerWrapper accessibilityMgr) {
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mToggleSliderFactory = factory;
+ AccessibilityManagerWrapper accessibilityMgr
+ ) {
+ mToggleSliderFactory = brightnessSliderfactory;
+ mBrightnessControllerFactory = brightnessControllerFactory;
mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
mAccessibilityMgr = accessibilityMgr;
}
@@ -121,8 +112,7 @@ public class BrightnessDialog extends Activity {
controller.init();
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
- mBrightnessController = new BrightnessController(
- this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
+ mBrightnessController = mBrightnessControllerFactory.create(controller);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
new file mode 100644
index 000000000000..45fc68a793bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.shade
+
+import android.os.PowerManager
+import android.view.GestureDetector
+import android.view.MotionEvent
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/**
+ * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
+ * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
+ *
+ * Touches go through the [NotificationShadeWindowViewController].
+ */
+@SysUISingleton
+class LockscreenHostedDreamGestureListener
+@Inject
+constructor(
+ private val falsingManager: FalsingManager,
+ private val powerInteractor: PowerInteractor,
+ private val statusBarStateController: StatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeLogger: ShadeLogger,
+) : GestureDetector.SimpleOnGestureListener() {
+ private val TAG = this::class.simpleName
+
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ if (shouldHandleMotionEvent()) {
+ if (!falsingManager.isFalseTap(LOW_PENALTY)) {
+ shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
+ powerInteractor.wakeUpIfDreaming(
+ "DREAMING_SINGLE_TAP",
+ PowerManager.WAKE_REASON_TAP
+ )
+ } else {
+ shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun shouldHandleMotionEvent(): Boolean {
+ return keyguardRepository.isActiveDreamLockscreenHosted.value &&
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ !primaryBouncerInteractor.isBouncerShowing()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ea15035a6c6f..9399d48be5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -168,7 +168,6 @@ import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -224,6 +223,8 @@ import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -234,8 +235,6 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
-
import kotlinx.coroutines.CoroutineDispatcher;
@SysUISingleton
@@ -440,8 +439,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final FalsingCollector mFalsingCollector;
private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
- private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
- new ShadeNotificationPresenterImpl();
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
@@ -1473,7 +1470,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// so we should not add a padding for them
stackScrollerPadding = 0;
} else {
- stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
+ stackScrollerPadding = mQsController.getHeaderHeight();
}
} else {
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1520,7 +1517,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
userSwitcherPreferredY,
darkAmount, mOverStretchAmount,
bypassEnabled,
- mQsController.getUnlockedStackScrollerPadding(),
+ mQsController.getHeaderHeight(),
mQsController.computeExpansionFraction(),
mDisplayTopInset,
mSplitShadeEnabled,
@@ -3323,23 +3320,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
).printTableData(ipw);
}
- private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
- @Override
- public RemoteInputController.Delegate createRemoteInputDelegate() {
- return mNotificationStackScrollLayoutController.createDelegate();
- }
-
- @Override
- public boolean hasPulsingNotifications() {
- return mNotificationListContainer.hasPulsingNotifications();
- }
- }
-
- @Override
- public ShadeNotificationPresenter getShadeNotificationPresenter() {
- return mShadeNotificationPresenter;
- }
-
@Override
public void initDependencies(
CentralSurfaces centralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 481da52635f0..1f401fbbe6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -543,7 +543,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
state.forceUserActivity,
state.launchingActivityFromNotification,
state.mediaBackdropShowing,
- state.wallpaperSupportsAmbientMode,
state.windowNotTouchable,
state.componentsForcingTopUi,
state.forceOpenTokens,
@@ -734,12 +733,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
apply(mCurrentState);
}
- @Override
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
- mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
- apply(mCurrentState);
- }
-
/**
* @param state The {@link StatusBarStateController} of the status bar.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 7812f07fc59c..d25294343d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -46,7 +46,6 @@ class NotificationShadeWindowState(
@JvmField var forceUserActivity: Boolean = false,
@JvmField var launchingActivityFromNotification: Boolean = false,
@JvmField var mediaBackdropShowing: Boolean = false,
- @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
@JvmField var windowNotTouchable: Boolean = false,
@JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
@JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
@@ -84,7 +83,6 @@ class NotificationShadeWindowState(
forceUserActivity.toString(),
launchingActivityFromNotification.toString(),
mediaBackdropShowing.toString(),
- wallpaperSupportsAmbientMode.toString(),
windowNotTouchable.toString(),
componentsForcingTopUi.toString(),
forceOpenTokens.toString(),
@@ -124,7 +122,6 @@ class NotificationShadeWindowState(
forceUserActivity: Boolean,
launchingActivity: Boolean,
backdropShowing: Boolean,
- wallpaperSupportsAmbientMode: Boolean,
notTouchable: Boolean,
componentsForcingTopUi: MutableSet<String>,
forceOpenTokens: MutableSet<Any>,
@@ -153,7 +150,6 @@ class NotificationShadeWindowState(
this.forceUserActivity = forceUserActivity
this.launchingActivityFromNotification = launchingActivity
this.mediaBackdropShowing = backdropShowing
- this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
this.windowNotTouchable = notTouchable
this.componentsForcingTopUi.clear()
this.componentsForcingTopUi.addAll(componentsForcingTopUi)
@@ -200,7 +196,6 @@ class NotificationShadeWindowState(
"forceUserActivity",
"launchingActivity",
"backdropShowing",
- "wallpaperSupportsAmbientMode",
"notTouchable",
"componentsForcingTopUi",
"forceOpenTokens",
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5c41d572149c..108ea68ae8e0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -46,6 +47,7 @@ import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -72,7 +74,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.util.time.SystemClock;
@@ -87,7 +88,7 @@ import javax.inject.Provider;
/**
* Controller for {@link NotificationShadeWindowView}.
*/
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
public class NotificationShadeWindowViewController {
private static final String TAG = "NotifShadeWindowVC";
private final FalsingCollector mFalsingCollector;
@@ -102,9 +103,12 @@ public class NotificationShadeWindowViewController {
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
private final boolean mIsTrackpadCommonEnabled;
+ private final FeatureFlags mFeatureFlags;
private GestureDetector mPulsingWakeupGestureHandler;
+ private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
@@ -156,6 +160,7 @@ public class NotificationShadeWindowViewController {
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
+ LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
@@ -187,8 +192,10 @@ public class NotificationShadeWindowViewController {
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mFeatureFlags = featureFlags;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -237,7 +244,10 @@ public class NotificationShadeWindowViewController {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
-
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+ mLockscreenHostedDreamGestureListener);
+ }
mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
@@ -291,6 +301,10 @@ public class NotificationShadeWindowViewController {
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ if (mDreamingWakeupGestureHandler != null
+ && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
+ return true;
+ }
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fba01201190e..5c1dd5670d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -29,6 +29,8 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -36,6 +38,7 @@ import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -54,7 +57,10 @@ class NotificationsQSContainerController @Inject constructor(
private val shadeHeaderController: ShadeHeaderController,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val featureFlags: FeatureFlags,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var qsExpanded = false
@@ -118,6 +124,9 @@ class NotificationsQSContainerController @Inject constructor(
isGestureNavigation = QuickStepContract.isGesturalMode(mode)
}
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+
+ mView.setStackScroller(notificationStackScrollLayoutController.getView())
+ mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
}
public override fun onViewAttached() {
@@ -254,14 +263,17 @@ class NotificationsQSContainerController @Inject constructor(
}
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ return
+ }
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+ val nsslId = R.id.notification_stack_scroller
constraintSet.apply {
- connect(R.id.notification_stack_scroller, START, startConstraintId, START)
- setMargin(R.id.notification_stack_scroller, START,
- if (splitShadeEnabled) 0 else panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, TOP, topMargin)
- setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+ connect(nsslId, START, startConstraintId, START)
+ setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(nsslId, END, panelMarginHorizontal)
+ setMargin(nsslId, TOP, topMargin)
+ setMargin(nsslId, BOTTOM, notificationsBottomMargin)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd86514..3b3df50f1520 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -23,6 +23,7 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowInsets;
import androidx.annotation.Nullable;
@@ -56,6 +57,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
private QS mQs;
private View mQSContainer;
private int mLastQSPaddingBottom;
+ private boolean mIsMigratingNSSL;
/**
* These are used to compute the bounding box containing the shade and the notification scrim,
@@ -75,10 +77,13 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
protected void onFinishInflate() {
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
- mStackScroller = findViewById(R.id.notification_stack_scroller);
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
+ void setStackScroller(View stackScroller) {
+ mStackScroller = stackScroller;
+ }
+
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
@@ -108,7 +113,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
}
public void setNotificationsMarginBottom(int margin) {
- LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+ MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
params.bottomMargin = margin;
mStackScroller.setLayoutParams(params);
}
@@ -173,8 +178,15 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
super.dispatchDraw(canvas);
}
+ void setMigratingNSSL(boolean isMigrating) {
+ mIsMigratingNSSL = isMigrating;
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (mIsMigratingNSSL) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
if (layoutIndex >= 0) {
return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 025c461110ef..baac57ca44ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -233,7 +233,6 @@ public class QuickSettingsController implements Dumpable {
private int mMaxExpansionHeight;
/** Expansion fraction of the notification shade */
private float mShadeExpandedFraction;
- private int mPeekHeight;
private float mLastOverscroll;
private boolean mExpansionFromOverscroll;
private boolean mExpansionEnabledPolicy = true;
@@ -429,7 +428,6 @@ public class QuickSettingsController implements Dumpable {
final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius);
@@ -500,12 +498,7 @@ public class QuickSettingsController implements Dumpable {
}
int getHeaderHeight() {
- return mQs.getHeader().getHeight();
- }
-
- /** Returns the padding of the stackscroller when unlocked */
- int getUnlockedStackScrollerPadding() {
- return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+ return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
}
private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -2090,8 +2083,6 @@ public class QuickSettingsController implements Dumpable {
ipw.println(mMaxExpansionHeight);
ipw.print("mShadeExpandedFraction=");
ipw.println(mShadeExpandedFraction);
- ipw.print("mPeekHeight=");
- ipw.println(mPeekHeight);
ipw.print("mLastOverscroll=");
ipw.println(mLastOverscroll);
ipw.print("mExpansionFromOverscroll=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 8ec8d115de78..3c4ad7222576 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -203,6 +204,14 @@ abstract class ShadeModule {
return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
}
+ @Provides
+ @SysUISingleton
+ fun providesSharedNotificationContainer(
+ notificationShadeWindowView: NotificationShadeWindowView,
+ ): SharedNotificationContainer {
+ return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+ }
+
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 8d5c30b51677..2532bad1d7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -18,6 +18,7 @@ package com.android.systemui.shade
import android.view.ViewPropertyAnimator
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -63,6 +64,9 @@ interface ShadeSurface : ShadeViewController {
/** Animates the view from its current alpha to zero then runs the runnable. */
fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+ /** Returns the NSSL controller. */
+ val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 9aa5eb0cd68b..d5b5c87ec781 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -19,9 +19,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import com.android.systemui.statusbar.phone.KeyguardStatusBarView
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController
@@ -141,9 +139,6 @@ interface ShadeViewController {
/** Returns the StatusBarState. */
val barState: Int
- /** Returns the NSSL controller. */
- val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
@@ -261,9 +256,6 @@ interface ShadeViewController {
/** Returns the ShadeFoldAnimator. */
val shadeFoldAnimator: ShadeFoldAnimator
- /** Returns the ShadeNotificationPresenter. */
- val shadeNotificationPresenter: ShadeNotificationPresenter
-
companion object {
/**
* Returns a multiplicative factor to use when determining the falsing threshold for touches
@@ -325,16 +317,7 @@ interface ShadeFoldAnimator {
fun cancelFoldToAodAnimation()
/** Returns the main view of the shade. */
- val view: ViewGroup
-}
-
-/** Handles the shade's interactions with StatusBarNotificationPresenter. */
-interface ShadeNotificationPresenter {
- /** Returns a new delegate for some view controller pieces of the remote input process. */
- fun createRemoteInputDelegate(): RemoteInputController.Delegate
-
- /** Returns whether the screen has temporarily woken up to display notifications. */
- fun hasPulsingNotifications(): Boolean
+ val view: ViewGroup?
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
new file mode 100644
index 000000000000..287ac528385f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/** Empty implementation of ShadeViewController for variants with no shade. */
+class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+ override fun expand(animate: Boolean) {}
+ override fun expandToQs() {}
+ override fun expandToNotifications() {}
+ override val isExpandingOrCollapsing: Boolean = false
+ override val isExpanded: Boolean = false
+ override val isPanelExpanded: Boolean = false
+ override val isShadeFullyExpanded: Boolean = false
+ override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapseWithDuration(animationDuration: Int) {}
+ override fun instantCollapse() {}
+ override fun animateCollapseQs(fullyCollapse: Boolean) {}
+ override fun canBeCollapsed(): Boolean = false
+ override val isCollapsing: Boolean = false
+ override val isFullyCollapsed: Boolean = false
+ override val isTracking: Boolean = false
+ override val isViewEnabled: Boolean = false
+ override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
+ override fun shouldHideStatusBarIconsWhenExpanded() = false
+ override fun blockExpansionForCurrentTouch() {}
+ override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+ override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
+ override fun startExpandLatencyTracking() {}
+ override fun startBouncerPreHideAnimation() {}
+ override fun dozeTimeTick() {}
+ override fun resetViews(animate: Boolean) {}
+ override val barState: Int = 0
+ override fun applyLaunchAnimationProgress(linearProgress: Float) {}
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+ override fun onBackPressed() {}
+ override fun setIsLaunchAnimationRunning(running: Boolean) {}
+ override fun setAlpha(alpha: Int, animate: Boolean) {}
+ override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+ override fun setPulsing(pulsing: Boolean) {}
+ override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+ override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+ override fun updateSystemUiStateFlags() {}
+ override fun updateTouchableRegion() {}
+ override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun postToView(action: Runnable): Boolean {
+ return false
+ }
+ override fun transitionToExpandedShade(delay: Long) {}
+ override val isUnlockHintRunning: Boolean = false
+
+ override fun resetViewGroupFade() {}
+ override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+ override fun setOverStretchAmount(amount: Float) {}
+ override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+ override fun showAodUi() {}
+ override fun isFullyExpanded(): Boolean {
+ return false
+ }
+ override fun handleExternalTouch(event: MotionEvent): Boolean {
+ return false
+ }
+ override fun startTrackingExpansionFromStatusBar() {}
+ override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
+ override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+}
+
+class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
+ override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun setHeadsUpAppearanceController(
+ headsUpAppearanceController: HeadsUpAppearanceController?
+ ) {}
+ override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
+}
+
+class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
+ override fun prepareFoldToAodAnimation() {}
+ override fun startFoldToAodAnimation(
+ startAction: Runnable,
+ endAction: Runnable,
+ cancelAction: Runnable,
+ ) {}
+ override fun cancelFoldToAodAnimation() {}
+ override val view: ViewGroup? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 47a4641bcdd9..5ac542b3530f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -112,9 +112,6 @@ public interface NotificationShadeWindowController extends RemoteInputController
/** Sets the state of whether heads up is showing or not. */
default void setHeadsUpShowing(boolean showing) {}
- /** Sets whether the wallpaper supports ambient mode or not. */
- default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
-
/** Gets whether the wallpaper is showing or not. */
default boolean isShowingWallpaper() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 5f28ecb56b8c..577ad20cb5d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,27 +16,21 @@
package com.android.systemui.statusbar.notification
-import android.content.Context
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
-class NotifPipelineFlags @Inject constructor(
- val context: Context,
- val featureFlags: FeatureFlags,
- val sysPropFlags: FlagResolver,
+class NotifPipelineFlags
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+ private val sysPropFlags: FlagResolver,
) {
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
fun allowDismissOngoing(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
-
- fun isOtpRedactionEnabled(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
-
- val isNoHunForOldWhenEnabled: Boolean
- get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+ sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
new file mode 100644
index 000000000000..d268e358690f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
+ * stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
+ */
+@CoordinatorScope
+class DreamCoordinator
+@Inject
+constructor(
+ private val statusBarStateController: SysuiStatusBarStateController,
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+) : Coordinator {
+ private var isOnKeyguard = false
+ private var isLockscreenHostedDream = false
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addPreGroupFilter(filter)
+ statusBarStateController.addCallback(statusBarStateListener)
+ scope.launch { attachFilterOnDreamingStateChange() }
+ recordStatusBarState(statusBarStateController.state)
+ }
+
+ private val filter =
+ object : NotifFilter("LockscreenHostedDreamFilter") {
+ var isFiltering = false
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+ return isFiltering
+ }
+ inline fun update(msg: () -> String) {
+ val wasFiltering = isFiltering
+ isFiltering = isLockscreenHostedDream && isOnKeyguard
+ if (wasFiltering != isFiltering) {
+ invalidateList(msg())
+ }
+ }
+ }
+
+ private val statusBarStateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ recordStatusBarState(newState)
+ }
+ }
+
+ private suspend fun attachFilterOnDreamingStateChange() {
+ keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
+ recordDreamingState(isDreaming)
+ }
+ }
+
+ private fun recordStatusBarState(newState: Int) {
+ isOnKeyguard = newState == StatusBarState.KEYGUARD
+ filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
+ }
+
+ private fun recordDreamingState(isDreaming: Boolean) {
+ isLockscreenHostedDream = isDreaming
+ filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e5953cfc07cd..0ccab9e46b72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -31,32 +33,34 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- sectionStyleProvider: SectionStyleProvider,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- groupWhenCoordinator: GroupWhenCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator,
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator,
+ dreamCoordinator: DreamCoordinator,
) : NotifCoordinators {
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -96,6 +100,10 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(remoteInputCoordinator)
mCoordinators.add(dismissibilityCoordinator)
+ if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mCoordinators.add(dreamCoordinator)
+ }
+
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 609f9d47a983..0c43da066fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -582,10 +582,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
}
private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
- if (!mFlags.isNoHunForOldWhenEnabled()) {
- return false;
- }
-
final Notification notification = entry.getSbn().getNotification();
if (notification == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index b2a3780c1024..867e08b2e743 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
@@ -58,9 +59,13 @@ public abstract class NotificationRowModule {
@ElementsIntoSet
@Named(NOTIF_REMOTEVIEWS_FACTORIES)
static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ PrecomputedTextViewFactory precomputedTextViewFactory
) {
final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+ if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
+ replacementFactories.add(precomputedTextViewFactory);
+ }
return replacementFactories;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
new file mode 100644
index 000000000000..57c82dc8d47a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import com.android.internal.widget.ImageFloatingTextView
+
+/** Precomputed version of [ImageFloatingTextView] */
+@RemoteViews.RemoteView
+class PrecomputedImageFloatingTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
new file mode 100644
index 000000000000..8508b1fd8e6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import android.widget.TextView
+
+/**
+ * A user interface element that uses the PrecomputedText API to display text in a notification,
+ * with the help of RemoteViews.
+ */
+@RemoteViews.RemoteView
+class PrecomputedTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ TextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
new file mode 100644
index 000000000000..b0023305ecdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import com.android.internal.widget.ImageFloatingTextView
+import javax.inject.Inject
+
+class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
+ override fun instantiate(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ TextView::class.java.name,
+ TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
+ ImageFloatingTextView::class.java.name ->
+ PrecomputedImageFloatingTextView(context, attrs)
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
new file mode 100644
index 000000000000..49f4a33ed3e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.text.PrecomputedText
+import android.text.Spannable
+import android.util.Log
+import android.widget.TextView
+
+internal interface TextPrecomputer {
+ /**
+ * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
+ * the textview on main thread.
+ *
+ * @param text text to be converted to PrecomputedText
+ * @return Runnable that sets precomputed text on the main thread
+ */
+ fun precompute(
+ textView: TextView,
+ text: CharSequence?,
+ logException: Boolean = true
+ ): Runnable {
+ val precomputedText: Spannable? =
+ text?.let { PrecomputedText.create(it, textView.textMetricsParams) }
+
+ return Runnable {
+ try {
+ textView.text = precomputedText
+ } catch (exception: IllegalArgumentException) {
+ if (logException) {
+ Log.wtf(
+ /* tag = */ TAG,
+ /* msg = */ "PrecomputedText setText failed for TextView:$textView",
+ /* tr = */ exception
+ )
+ }
+ textView.text = text
+ }
+ }
+ }
+
+ private companion object {
+ private const val TAG = "TextPrecomputer"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 8063c8cfc6f3..c1ceb3ce5ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3758,20 +3758,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
- debugLog("handleEmptySpaceClick: touch event propagated further");
+ debugShadeLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
default:
- debugLog("handleEmptySpaceClick: MotionEvent ignored");
+ debugShadeLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
- private void debugLog(@CompileTimeConstant final String s) {
+ private void debugShadeLog(@CompileTimeConstant final String s) {
if (mLogger == null) {
return;
}
- mLogger.d(s);
+ mLogger.logShadeDebugEvent(s);
}
private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 2c38b8dfca04..3396306412bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -7,6 +7,7 @@ import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
@@ -19,7 +20,8 @@ import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer,
- @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
+ @NotificationRenderLog private val notificationRenderBuffer: LogBuffer,
+ @ShadeLog private val shadeLogBuffer: LogBuffer,
) {
fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
@@ -63,7 +65,7 @@ class NotificationStackScrollLogger @Inject constructor(
})
}
- fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+ fun logShadeDebugEvent(@CompileTimeConstant msg: String) = shadeLogBuffer.log(TAG, DEBUG, msg)
fun logEmptySpaceClick(
isBelowLastNotification: Boolean,
@@ -71,7 +73,7 @@ class NotificationStackScrollLogger @Inject constructor(
touchIsClick: Boolean,
motionEventDesc: String
) {
- buffer.log(TAG, DEBUG, {
+ shadeLogBuffer.log(TAG, DEBUG, {
int1 = statusBarState
bool1 = touchIsClick
bool2 = isBelowLastNotification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
new file mode 100644
index 000000000000..874450b36bf0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business-logic specifically related to the shared notification stack container. */
+class SharedNotificationContainerInteractor
+@Inject
+constructor(
+ configurationRepository: ConfigurationRepository,
+ private val context: Context,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ configurationRepository.onAnyConfigurationChange
+ .onStart { emit(Unit) }
+ .map { _ ->
+ with(context.resources) {
+ ConfigurationBasedDimensions(
+ useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
+ useLargeScreenHeader =
+ getBoolean(R.bool.config_use_large_screen_shade_header),
+ marginHorizontal =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
+ marginBottom =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+ marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
+ marginTopLargeScreen =
+ getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+ )
+ }
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val useSplitShade: Boolean,
+ val useLargeScreenHeader: Boolean,
+ val marginHorizontal: Int,
+ val marginBottom: Int,
+ val marginTop: Int,
+ val marginTopLargeScreen: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
new file mode 100644
index 000000000000..688843de06f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+
+/**
+ * Container for the stack scroller, so that the bounds can be externally specified, such as from
+ * the keyguard or shade scenes.
+ */
+class SharedNotificationContainer(
+ context: Context,
+ private val attrs: AttributeSet?,
+) :
+ ConstraintLayout(
+ context,
+ attrs,
+ ) {
+
+ private val baseConstraintSet = ConstraintSet()
+
+ init {
+ baseConstraintSet.apply {
+ create(R.id.nssl_guideline, VERTICAL)
+ setGuidelinePercent(R.id.nssl_guideline, 0.5f)
+ }
+ baseConstraintSet.applyTo(this)
+ }
+
+ fun addNotificationStackScrollLayout(nssl: View) {
+ addView(nssl)
+ }
+
+ fun updateConstraints(
+ useSplitShade: Boolean,
+ marginStart: Int,
+ marginTop: Int,
+ marginEnd: Int,
+ marginBottom: Int
+ ) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(baseConstraintSet)
+
+ val startConstraintId =
+ if (useSplitShade) {
+ R.id.nssl_guideline
+ } else {
+ PARENT_ID
+ }
+ val nsslId = R.id.notification_stack_scroller
+ constraintSet.apply {
+ connect(nsslId, START, startConstraintId, START)
+ connect(nsslId, END, PARENT_ID, END)
+ connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
+ connect(nsslId, TOP, PARENT_ID, TOP)
+ setMargin(nsslId, START, marginStart)
+ setMargin(nsslId, END, marginEnd)
+ setMargin(nsslId, TOP, marginTop)
+ setMargin(nsslId, BOTTOM, marginBottom)
+ }
+ constraintSet.applyTo(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
new file mode 100644
index 000000000000..fb1d55d6bb7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object SharedNotificationContainerBinder {
+
+ @JvmStatic
+ fun bind(
+ view: SharedNotificationContainer,
+ viewModel: SharedNotificationContainerViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.configurationBasedDimensions.collect {
+ view.updateConstraints(
+ useSplitShade = it.useSplitShade,
+ marginStart = it.marginStart,
+ marginTop = it.marginTop,
+ marginEnd = it.marginEnd,
+ marginBottom = it.marginBottom,
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
new file mode 100644
index 000000000000..b2e5ac116e46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the shared notification container */
+class SharedNotificationContainerViewModel
+@Inject
+constructor(
+ interactor: SharedNotificationContainerInteractor,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ interactor.configurationBasedDimensions
+ .map {
+ ConfigurationBasedDimensions(
+ marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+ marginEnd = it.marginHorizontal,
+ marginBottom = it.marginBottom,
+ marginTop =
+ if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+ useSplitShade = it.useSplitShade,
+ )
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val marginStart: Int,
+ val marginTop: Int,
+ val marginEnd: Int,
+ val marginBottom: Int,
+ val useSplitShade: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 2d8f371aadac..1f9c9f2b8c14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -160,6 +160,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private KeyguardViewController mKeyguardViewController;
private DozeScrimController mDozeScrimController;
private KeyguardViewMediator mKeyguardViewMediator;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
@@ -280,7 +281,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
LatencyTracker latencyTracker,
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
- SystemClock systemClock
+ SystemClock systemClock,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -308,6 +310,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mVibratorHelper = vibrator;
mLogger = biometricUnlockLogger;
mSystemClock = systemClock;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -449,8 +452,19 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
Runnable wakeUp = ()-> {
- if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
+ // Check to see if we are still locked when we are waking and unlocking from dream.
+ // This runnable should be executed after unlock. If that's true, we could be not
+ // dreaming, but still locked. In this case, we should attempt to authenticate instead
+ // of waking up.
+ if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ && !mKeyguardStateController.isUnlocked()
+ && !mUpdateMonitor.isDreaming()) {
+ // Post wakeUp runnable is called from a callback in keyguard.
+ mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated(
+ false /* primaryAuth */));
+ } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
mLogger.i("bio wakelock: Authenticated, waking up...");
+
mPowerManager.wakeUp(
mSystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_BIOMETRIC,
@@ -462,7 +476,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
Trace.endSection();
};
- if (mMode != MODE_NONE) {
+ if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) {
wakeUp.run();
}
switch (mMode) {
@@ -484,6 +498,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
+ // In the case of waking and unlocking from dream, waking up is delayed until after
+ // unlock is complete to avoid conflicts during each sequence's transitions.
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp);
+ // Execution falls through here to proceed unlocking.
case MODE_WAKE_AND_UNLOCK_PULSING:
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2b9c3d33e9b8..acd6e49fe8c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -259,8 +259,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
void readyForKeyguardDone();
- void setLockscreenUser(int newUserId);
-
void showKeyguard();
boolean hideKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 62e98f934888..8361d6ba1801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -51,7 +51,6 @@ import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.TaskInfo;
import android.app.UiModeManager;
-import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -413,7 +412,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final Point mCurrentDisplaySize = new Point();
- protected NotificationShadeWindowView mNotificationShadeWindowView;
protected PhoneStatusBarView mStatusBarView;
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
@@ -456,7 +454,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
- protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
@@ -722,6 +721,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
+ Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
NotificationShelfController notificationShelfController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
DozeParameters dozeParameters,
@@ -825,6 +825,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy;
mNotificationShelfController = notificationShelfController;
mStackScrollerController = notificationStackScrollLayoutController;
mStackScroller = mStackScrollerController.getView();
@@ -978,16 +979,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
createAndAddWindows(result);
- if (mWallpaperSupported) {
- // Make sure we always have the most current wallpaper info.
- IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
- null /* handler */, UserHandle.ALL);
- mWallpaperChangedReceiver.onReceive(mContext, null);
- } else if (DEBUG) {
- Log.v(TAG, "start(): no wallpaper service ");
- }
-
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -1073,7 +1064,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mDozeServiceHost.initialize(
this,
mStatusBarKeyguardViewManager,
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mShadeSurface,
mAmbientIndicationContainer);
updateLightRevealScrimVisibility();
@@ -1235,8 +1226,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
- mWallpaperController.setRootView(mNotificationShadeWindowView);
+ getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
+ mWallpaperController.setRootView(getNotificationShadeWindowView());
// TODO: Deal with the ugliness that comes from having some of the status bar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
@@ -1257,7 +1248,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mStatusBarView = statusBarView;
mPhoneStatusBarViewController = statusBarViewController;
mStatusBarTransitions = statusBarTransitions;
- mNotificationShadeWindowViewController
+ getNotificationShadeWindowViewController()
.setStatusBarViewController(mPhoneStatusBarViewController);
// Ensure we re-propagate panel expansion values to the panel controller and
// any listeners it may have, such as PanelBar. This will also ensure we
@@ -1271,7 +1262,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mStatusBarInitializer.initializeStatusBar(
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
- mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
+ mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
createNavigationBar(result);
@@ -1279,7 +1270,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
+ mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
mAutoHideController.setStatusBar(new AutoHideUiElement() {
@@ -1304,10 +1295,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
});
- ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
- ScrimView notificationsScrim = mNotificationShadeWindowView
+ ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
+ ScrimView notificationsScrim = getNotificationShadeWindowView()
.findViewById(R.id.scrim_notifications);
- ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+ ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front);
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1345,7 +1336,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationShelfController,
mHeadsUpManager);
- BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
+ BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
mMediaManager.setup(null, null, null, mScrimController, null);
} else {
@@ -1364,7 +1355,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
});
// Set up the quick settings tile panel
- final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
+ final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
@@ -1379,7 +1370,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
.withDefault(this::createDefaultQSFragment)
.build());
mBrightnessMirrorController = new BrightnessMirrorController(
- mNotificationShadeWindowView,
+ getNotificationShadeWindowView(),
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
@@ -1396,7 +1387,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
});
}
- mReportRejectedTouch = mNotificationShadeWindowView
+ mReportRejectedTouch = getNotificationShadeWindowView()
.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
updateReportRejectedTouchVisibility();
@@ -1544,7 +1535,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
protected QS createDefaultQSFragment() {
return mFragmentService
- .getFragmentHostManager(mNotificationShadeWindowView)
+ .getFragmentHostManager(getNotificationShadeWindowView())
.create(QSFragment.class);
}
@@ -1553,7 +1544,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mNotifListContainer,
mHeadsUpManager,
mJankMonitor);
@@ -1592,7 +1583,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
mShadeController.onStatusBarTouch(event);
- return mNotificationShadeWindowView.onTouchEvent(event);
+ return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1606,15 +1597,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView();
- mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
- mNotificationShadeWindowViewController = mCentralSurfacesComponent
- .getNotificationShadeWindowViewController();
// TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller.
// (Right now, there's a circular dependency.)
mNotificationShadeWindowController.setWindowRootView(windowRootView);
- mNotificationShadeWindowViewController.setupExpandedStatusBar();
+ getNotificationShadeWindowViewController().setupExpandedStatusBar();
mShadeController.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
+ getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
@@ -1633,6 +1621,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mCommandQueue.addCallback(mCommandQueueCallbacks);
}
+ protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+ return mNotificationShadeWindowViewControllerLazy.get();
+ }
+
+ protected NotificationShadeWindowView getNotificationShadeWindowView() {
+ return getNotificationShadeWindowViewController().getView();
+ }
+
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mStatusBarStateController.addCallback(mStateListener,
@@ -1688,7 +1684,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public AuthKeyguardMessageArea getKeyguardMessageArea() {
- return mNotificationShadeWindowViewController.getKeyguardMessageArea();
+ return getNotificationShadeWindowViewController().getKeyguardMessageArea();
}
@Override
@@ -1982,11 +1978,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
pw.println(" ShadeWindowView: ");
- if (mNotificationShadeWindowViewController != null) {
- mNotificationShadeWindowViewController.dump(pw, args);
- CentralSurfaces.dumpBarTransitions(
- pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
- }
+ getNotificationShadeWindowViewController().dump(pw, args);
+ CentralSurfaces.dumpBarTransitions(
+ pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
pw.println(" mMediaManager: ");
if (mMediaManager != null) {
@@ -2159,18 +2153,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
};
/**
- * Notify the shade controller that the current user changed
- *
- * @param newUserId userId of the new user
- */
- @Override
- public void setLockscreenUser(int newUserId) {
- if (mWallpaperSupported) {
- mWallpaperChangedReceiver.onReceive(mContext, null);
- }
- }
-
- /**
* Reload some of our resources when the configuration changes.
*
* We don't reload everything when the configuration changes -- we probably
@@ -2860,7 +2842,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
updateVisibleToUser();
updateNotificationPanelTouchState();
- mNotificationShadeWindowViewController.cancelCurrentTouch();
+ getNotificationShadeWindowViewController().cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3172,12 +3154,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
updateScrimController();
}
- @VisibleForTesting
- public void setNotificationShadeWindowViewController(
- NotificationShadeWindowViewController nswvc) {
- mNotificationShadeWindowViewController = nswvc;
- }
-
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -3412,7 +3388,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mVisible = visible;
if (visible) {
DejankUtils.notifyRendererOfExpensiveFrame(
- mNotificationShadeWindowView, "onShadeVisibilityChanged");
+ getNotificationShadeWindowView(), "onShadeVisibilityChanged");
} else {
mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
@@ -3550,30 +3526,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
};
- private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mWallpaperSupported) {
- // Receiver should not have been registered at all...
- Log.wtf(TAG, "WallpaperManager not supported");
- return;
- }
- WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
- mUserTracker.getUserId());
- mWallpaperController.onWallpaperInfoUpdated(info);
-
- final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- // If WallpaperInfo is null, it must be ImageWallpaper.
- final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && (info != null && info.supportsAmbientMode());
-
- mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- }
- };
-
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 7312db6595e5..ed9722e04da0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -311,6 +311,7 @@ public final class DozeServiceHost implements DozeHost {
@Override
public void dozeTimeTick() {
+ mDozeInteractor.dozeTimeTick();
mNotificationPanel.dozeTimeTick();
mAuthController.dozeTimeTick();
if (mAmbientIndicationContainer instanceof DozeReceiver) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index ff1b31d8848f..924aac4e70be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -50,12 +50,6 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
@DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
private var pendingUnlock: PendingUnlock? = null
private val listeners = mutableListOf<OnBypassStateChangedListener>()
- private val postureCallback = DevicePostureController.Callback { posture ->
- if (postureState != posture) {
- postureState = posture
- notifyListeners()
- }
- }
private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
override fun onFaceAuthEnabledChanged() = notifyListeners()
}
@@ -162,10 +156,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
val dismissByDefault = if (context.resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
- tunerService.addTunable(object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
- }
+ tunerService.addTunable({ key, _ ->
+ bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
}, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
lockscreenUserManager.addUserChangedListener(
object : NotificationLockscreenUserManager.UserChangedListener {
@@ -281,8 +273,6 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
}
companion object {
- const val BYPASS_FADE_DURATION = 67
-
private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
private const val FACE_UNLOCK_BYPASS_NEVER = 2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index f26a84ba20a3..dc5eb0d281c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -38,8 +38,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
@@ -65,7 +63,6 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final boolean mUseNewLightBarLogic;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
@@ -123,10 +120,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -188,51 +183,31 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
final boolean last = mNavigationLight;
mHasLightNavigationBar = isLight(appearance, navigationBarMode,
APPEARANCE_LIGHT_NAVIGATION_BARS);
- if (mUseNewLightBarLogic) {
- final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
- final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
- final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
- final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
- final boolean darkForTop = darkForQs || mGlobalActionsVisible;
- mNavigationLight =
- ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" ignoreScrimForce=").append(ignoreScrimForce)
- .append(" darkForScrim=").append(darkForScrim)
- .append(" lightForScrim=").append(lightForScrim)
- .append(" darkForQs=").append(darkForQs)
- .append(" darkForTop=").append(darkForTop)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
- } else {
- mNavigationLight = mHasLightNavigationBar
- && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
- && !mQsCustomizing;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
+ final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+ final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+ final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+ final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
+ final boolean darkForTop = darkForQs || mGlobalActionsVisible;
+ mNavigationLight =
+ ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
+ if (DEBUG_NAVBAR) {
+ mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+ .append("onNavigationBarAppearanceChanged()")
+ .append(" appearance=").append(appearance)
+ .append(" nbModeChanged=").append(nbModeChanged)
+ .append(" navigationBarMode=").append(navigationBarMode)
+ .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" ignoreScrimForce=").append(ignoreScrimForce)
+ .append(" darkForScrim=").append(darkForScrim)
+ .append(" lightForScrim=").append(lightForScrim)
+ .append(" darkForQs=").append(darkForQs)
+ .append(" darkForTop=").append(darkForTop)
+ .append(" mNavigationLight=").append(mNavigationLight)
+ .append(" last=").append(last)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
}
if (mNavigationLight != last) {
updateNavigation();
@@ -311,66 +286,39 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
- if (mUseNewLightBarLogic) {
- boolean bouncerVisibleLast = mBouncerVisible;
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- boolean forceLightForScrimLast = mForceLightForScrim;
- mBouncerVisible =
- scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
- final boolean forceForScrim = mBouncerVisible
- || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
- final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
-
- mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
- mForceLightForScrim = forceForScrim && scrimColorIsLight;
- if (mBouncerVisible != bouncerVisibleLast) {
- reevaluate();
- } else if (mHasLightNavigationBar) {
- if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
- } else {
- if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" forceForScrim=").append(forceForScrim)
- .append(" scrimColorIsLight=").append(scrimColorIsLight)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mBouncerVisible=").append(mBouncerVisible)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" mForceLightForScrim=").append(mForceLightForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ boolean bouncerVisibleLast = mBouncerVisible;
+ boolean forceDarkForScrimLast = mForceDarkForScrim;
+ boolean forceLightForScrimLast = mForceLightForScrim;
+ mBouncerVisible =
+ scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
+ final boolean forceForScrim = mBouncerVisible
+ || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+ final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+
+ mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+ mForceLightForScrim = forceForScrim && scrimColorIsLight;
+ if (mBouncerVisible != bouncerVisibleLast) {
+ reevaluate();
+ } else if (mHasLightNavigationBar) {
+ if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
} else {
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
- // This enables IMEs to control the navigation bar color.
- // For other cases, scrim should be able to veto the light navigation bar.
- // NOTE: this was also wrong for S and has been removed in the new logic.
- mForceDarkForScrim = scrimState != ScrimState.BOUNCER
- && scrimState != ScrimState.BOUNCER_SCRIMMED
- && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
- && !scrimInFrontColor.supportsDarkText();
- if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
- reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+ }
+ if (DEBUG_NAVBAR) {
+ mLastSetScrimStateLog = getLogStringBuilder()
+ .append("setScrimState()")
+ .append(" scrimState=").append(scrimState)
+ .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+ .append(" scrimInFrontColor=").append(scrimInFrontColor)
+ .append(" forceForScrim=").append(forceForScrim)
+ .append(" scrimColorIsLight=").append(scrimColorIsLight)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mBouncerVisible=").append(mBouncerVisible)
+ .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+ .append(" mForceLightForScrim=").append(mForceLightForScrim)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
}
}
@@ -498,7 +446,6 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private final DarkIconDispatcher mDarkIconDispatcher;
private final BatteryController mBatteryController;
private final NavigationModeController mNavModeController;
- private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
private final DisplayTracker mDisplayTracker;
@@ -507,14 +454,12 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
mDarkIconDispatcher = darkIconDispatcher;
mBatteryController = batteryController;
mNavModeController = navModeController;
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mDisplayTracker = displayTracker;
}
@@ -522,7 +467,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
/** Create an {@link LightBarController} */
public LightBarController create(Context context) {
return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
- mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
+ mNavModeController, mDumpManager, mDisplayTracker);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f0fc1432c5a3..862f169b2176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,6 +30,7 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -51,6 +52,7 @@ class PhoneStatusBarViewController private constructor(
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
@@ -165,20 +167,20 @@ class PhoneStatusBarViewController private constructor(
if (event.action == MotionEvent.ACTION_DOWN) {
// If the view that would receive the touch is disabled, just have status
// bar eat the gesture.
- if (!centralSurfaces.shadeViewController.isViewEnabled) {
+ if (!shadeViewController.isViewEnabled) {
shadeLogger.logMotionEvent(event,
"onTouchForwardedFromStatusBar: panel view disabled")
return true
}
- if (centralSurfaces.shadeViewController.isFullyCollapsed &&
+ if (shadeViewController.isFullyCollapsed &&
event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
}
- centralSurfaces.shadeViewController.startTrackingExpansionFromStatusBar()
+ shadeViewController.startTrackingExpansionFromStatusBar()
}
- return centralSurfaces.shadeViewController.handleExternalTouch(event)
+ return shadeViewController.handleExternalTouch(event)
}
}
@@ -222,6 +224,7 @@ class PhoneStatusBarViewController private constructor(
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
@@ -241,6 +244,7 @@ class PhoneStatusBarViewController private constructor(
progressProvider.getOrNull(),
centralSurfaces,
shadeController,
+ shadeViewController,
shadeLogger,
statusBarMoveFromCenterAnimationController,
userChipViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e4e912e1ceec..e82ac59b6746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,6 +48,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
+import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -56,8 +57,6 @@ import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
@@ -71,8 +70,10 @@ import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -89,7 +90,8 @@ import kotlinx.coroutines.CoroutineDispatcher;
* security method gets shown).
*/
@SysUISingleton
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable,
+ CoreStartable {
static final String TAG = "ScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -207,6 +209,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
private final Handler mHandler;
private final Executor mMainExecutor;
+ private final JavaAdapter mJavaAdapter;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -249,8 +252,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- private final FeatureFlags mFeatureFlags;
- private final boolean mUseNewLightBarLogic;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -268,6 +269,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private boolean mKeyguardOccluded;
private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final WallpaperRepository mWallpaperRepository;
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@@ -297,18 +299,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
DockManager dockManager,
ConfigurationController configurationController,
@Main Executor mainExecutor,
+ JavaAdapter javaAdapter,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
- LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags) {
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mFeatureFlags = featureFlags;
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
@@ -317,6 +318,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
+ mJavaAdapter = javaAdapter;
mScreenOffAnimationController = screenOffAnimationController;
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", mHandler);
@@ -348,9 +350,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mColors = new GradientColors();
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+ this::setWallpaperSupportsAmbientMode);
+ }
+
/**
* Attach the controller to the supplied views.
*/
@@ -1153,13 +1163,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
if (mClipsQsScrim && mQsBottomVisible) {
alpha = mNotificationsAlpha;
}
- if (mUseNewLightBarLogic) {
- mScrimStateListener.accept(mState, alpha, mColors);
- } else {
- // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
- // when in fact they all share the same GradientColors instance, which we own.
- mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
- }
+ mScrimStateListener.accept(mState, alpha, mColors);
}
private void dispatchScrimsVisible() {
@@ -1483,15 +1487,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
mColors.setMainColor(background);
mColors.setSecondaryColor(accent);
- if (mUseNewLightBarLogic) {
- final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
- mColors.setSupportsDarkText(isBackgroundLight);
- } else {
- // NOTE: This was totally backward, but LightBarController was flipping it back.
- // There may be other consumers of this which would struggle though
- mColors.setSupportsDarkText(
- ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
- }
+ final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+ mColors.setSupportsDarkText(isBackgroundLight);
int surface = Utils.getColorAttr(mScrimBehind.getContext(),
com.android.internal.R.attr.materialColorSurface).getDefaultColor();
@@ -1551,7 +1548,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.println(mState.getMaxLightRevealScrimAlpha());
}
- public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ private void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 481cf3ceb197..9a295e63fb9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,6 +21,7 @@ import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -35,6 +36,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
+ private final NotificationStackScrollLayoutController mNsslController;
private final KeyguardBypassController mKeyguardBypassController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarStateController mStatusBarStateController;
@@ -45,14 +47,15 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
NotificationShadeWindowController notificationShadeWindowController,
StatusBarWindowController statusBarWindowController,
ShadeViewController shadeViewController,
+ NotificationStackScrollLayoutController nsslController,
KeyguardBypassController keyguardBypassController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController statusBarStateController,
NotificationRemoteInputManager notificationRemoteInputManager) {
-
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
mShadeViewController = shadeViewController;
+ mNsslController = nsslController;
mKeyguardBypassController = keyguardBypassController;
mHeadsUpManager = headsUpManager;
mStatusBarStateController = statusBarStateController;
@@ -85,8 +88,7 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
//animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mShadeViewController.getNotificationStackScrollLayoutController()
- .runAfterAnimationFinished(() -> {
+ mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 35285b222f63..a9135ac91e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -28,7 +28,6 @@ import android.service.vr.IVrStateCallbacks;
import android.util.Log;
import android.util.Slog;
import android.view.View;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
@@ -50,7 +49,6 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
@@ -59,7 +57,6 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -78,21 +75,18 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
private final NotifShadeEventSource mNotifShadeEventSource;
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
-
private final ShadeViewController mNotificationPanel;
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
private final CentralSurfaces mCentralSurfaces;
private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
private final CommandQueue mCommandQueue;
-
- private final AccessibilityManager mAccessibilityManager;
private final KeyguardManager mKeyguardManager;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
@@ -123,11 +117,9 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
NotifShadeEventSource notifShadeEventSource,
NotificationMediaManager notificationMediaManager,
NotificationGutsManager notificationGutsManager,
- LockscreenGestureLogger lockscreenGestureLogger,
InitController initController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationRemoteInputManager remoteInputManager,
- NotifPipelineFlags notifPipelineFlags,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
NotificationListContainer notificationListContainer) {
mActivityStarter = activityStarter;
@@ -139,6 +131,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
mNotificationsInteractor = notificationsInteractor;
+ mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
mCommandQueue = commandQueue;
@@ -149,10 +142,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
mGutsManager = notificationGutsManager;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
mNotificationShadeWindowController = notificationShadeWindowController;
- mNotifPipelineFlags = notifPipelineFlags;
mAboveShelfObserver.setListener(statusBarWindow.findViewById(
R.id.notification_container_parent));
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
@@ -170,7 +161,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
}
remoteInputManager.setUpWithCallback(
remoteInputManagerCallback,
- mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
+ mNsslController.createDelegate());
initController.addPostInitTask(() -> {
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
@@ -202,7 +193,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
}
private void maybeEndAmbientPulse() {
- if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+ if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
&& !mHeadsUpManager.hasNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
@@ -217,7 +208,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
// End old BaseStatusBar.userSwitched
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
- mCentralSurfaces.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
}
@@ -272,22 +262,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
}
};
- private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
- @Override
- public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
- // If the user has security enabled, show challenge if the setting is changed.
- if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && mKeyguardManager.isKeyguardLocked()) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- }
- };
-
private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
@Override
public void onSettingsClick(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 4ae460a3f0e1..e77f419f74e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -21,8 +21,6 @@ import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.ST
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.scene.ui.view.WindowRootView;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -77,16 +75,6 @@ public interface CentralSurfacesComponent {
WindowRootView getWindowRootView();
/**
- * Creates or returns a {@link NotificationShadeWindowView}.
- */
- NotificationShadeWindowView getNotificationShadeWindowView();
-
- /**
- * Creates a NotificationShadeWindowViewController.
- */
- NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
- /**
* Creates a StatusBarHeadsUpChangeListener.
*/
StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 27cc64f9a8e8..0e99c67a0d80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,12 +19,10 @@ package com.android.systemui.statusbar.pipeline.dagger
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +44,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -58,9 +58,9 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import kotlinx.coroutines.flow.Flow
import java.util.function.Supplier
import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
@Module
abstract class StatusBarPipelineModule {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bcf3b0cbfc86..24987abd7a85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@ public class StatusBarWindowController {
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
- new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+ .setInsetsSize(getInsets(height)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+ .setInsetsSize(getInsets(height)),
gestureInsetsProvider
};
return lp;
@@ -306,12 +308,37 @@ public class StatusBarWindowController {
mLpChanged.height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
mLpChanged.paramsForRotation[rot].height =
- state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT :
- SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
+ state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height;
+ // The status bar height could change at runtime if one display has a cutout while
+ // another doesn't (like some foldables). It could also change when using debug cutouts.
+ // So, we need to re-fetch the height and re-apply it to the insets each time to avoid
+ // bugs like b/290300359.
+ InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets;
+ if (providers != null) {
+ for (InsetsFrameProvider provider : providers) {
+ provider.setInsetsSize(getInsets(height));
+ }
+ }
}
}
+ /**
+ * Get the insets that should be applied to the status bar window given the current status bar
+ * height.
+ *
+ * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}.
+ * However, the status bar *insets* should *not* be full-screen, because this would prevent apps
+ * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead,
+ * the status bar insets should always be equal to the space occupied by the actual status bar
+ * content -- setting the insets correctly will prevent window manager from unnecessarily
+ * re-drawing this window and other windows. This method provides the correct insets.
+ */
+ private Insets getInsets(int height) {
+ return Insets.of(0, height, 0, 0);
+ }
+
private void apply(State state) {
if (!mIsAttached) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index c109eb4134bb..324ef4baf7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -58,6 +58,16 @@ class ToastLogger @Inject constructor(
})
}
+ fun logOnSkipToastForInvalidDisplay(packageName: String, token: String, displayId: Int) {
+ log(DEBUG, {
+ str1 = packageName
+ str2 = token
+ int1 = displayId
+ }, {
+ "[$str2] Skip toast for [$str1] scheduled on unavailable display #$int1"
+ })
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ed14c8ad150c..ae8128d20d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -32,6 +32,7 @@ import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.widget.ToastPresenter;
@@ -115,8 +116,14 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
Context context = mContext.createContextAsUser(userHandle, 0);
DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class);
- Context displayContext = context.createDisplayContext(
- mDisplayManager.getDisplay(displayId));
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Display for which this toast was scheduled for is no longer available.
+ mToastLogger.logOnSkipToastForInvalidDisplay(packageName, token.toString(),
+ displayId);
+ return;
+ }
+ Context displayContext = context.createDisplayContext(display);
mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
userHandle.getIdentifier(), mOrientation);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 5e489b0f38ac..82589d3ea1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -27,6 +27,7 @@ import com.android.systemui.recents.RecentsModule;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
import dagger.Subcomponent;
@@ -39,6 +40,7 @@ import dagger.Subcomponent;
DefaultComponentBinder.class,
DependencyProvider.class,
KeyguardModule.class,
+ NoopWallpaperModule.class,
NotificationRowModule.class,
NotificationsModule.class,
RecentsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index eed7950abacb..cbe402017c41 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -94,7 +94,7 @@ constructor(
wakefulnessLifecycle.addObserver(this)
// TODO(b/254878364): remove this call to NPVC.getView()
- getShadeFoldAnimator().view.repeatWhenAttached {
+ getShadeFoldAnimator().view?.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
}
@@ -161,10 +161,9 @@ constructor(
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
// TODO(b/254878364): remove this call to NPVC.getView()
- OneShotPreDrawListener.add(
- getShadeFoldAnimator().view,
- onReady
- )
+ getShadeFoldAnimator().view?.let {
+ OneShotPreDrawListener.add(it, onReady)
+ }
} else {
// No animation, call ready callback immediately
onReady.run()
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index db2aca873d0c..65a02184f96d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -16,32 +16,34 @@
package com.android.systemui.util
-import android.app.WallpaperInfo
import android.app.WallpaperManager
import android.util.Log
import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
import javax.inject.Inject
import kotlin.math.max
private const val TAG = "WallpaperController"
+/**
+ * Controller for wallpaper-related logic.
+ *
+ * Note: New logic should be added to [WallpaperRepository], not this class.
+ */
@SysUISingleton
-class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+class WallpaperController @Inject constructor(
+ private val wallpaperManager: WallpaperManager,
+ private val wallpaperRepository: WallpaperRepository,
+) {
var rootView: View? = null
private var notificationShadeZoomOut: Float = 0f
private var unfoldTransitionZoomOut: Float = 0f
- private var wallpaperInfo: WallpaperInfo? = null
-
- fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
- this.wallpaperInfo = wallpaperInfo
- }
-
private val shouldUseDefaultUnfoldTransition: Boolean
- get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+ get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
?: true
fun setNotificationShadeZoom(zoomOut: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 349f3684659c..87b2697611d2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -82,6 +83,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -120,6 +122,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -300,6 +303,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private final DevicePostureController mDevicePostureController;
private @DevicePostureController.DevicePostureInt int mDevicePosture;
private int mOrientation;
+ private final FeatureFlags mFeatureFlags;
public VolumeDialogImpl(
Context context,
@@ -315,7 +319,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
Looper looper,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
@@ -1319,12 +1325,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private void provideTouchFeedbackH(int newRingerMode) {
VibrationEffect effect = null;
+ int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
switch (newRingerMode) {
case RINGER_MODE_NORMAL:
mController.scheduleTouchFeedback();
break;
case RINGER_MODE_SILENT:
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
break;
case RINGER_MODE_VIBRATE:
// Feedback handled by onStateChange, for feedback both when user toggles
@@ -1332,8 +1340,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
}
- if (effect != null) {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mDialogView.performHapticFeedback(hapticConstant);
+ } else if (effect != null) {
mController.vibrate(effect);
}
}
@@ -1770,7 +1781,22 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ if (mShowing) {
+ // The dialog view is responsible for triggering haptics in the oneway API
+ mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
+ }
+ /*
+ TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
+ enabled by dragging the volume slider of Settings to a value of 0. This must be
+ handled by view Slices in Settings whilst using the performHapticFeedback API.
+ */
+
+ } else {
+ // Old behavior only active if the oneway API is not used.
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+ }
}
mState = state;
mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index d0edc6e7ce4c..cc9f3e14216e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,6 +22,7 @@ import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@ public interface VolumeModule {
InteractionJankMonitor interactionJankMonitor,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -76,7 +78,8 @@ public interface VolumeModule {
csdFactory,
devicePostureController,
Looper.getMainLooper(),
- dumpManager);
+ dumpManager,
+ featureFlags);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
new file mode 100644
index 000000000000..baf88b0d9cbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.NoopWallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface NoopWallpaperModule {
+ @Binds
+ @SysUISingleton
+ fun bindWallpaperRepository(impl: NoopWallpaperRepository): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
new file mode 100644
index 000000000000..1b899784038e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface WallpaperModule {
+ @Binds
+ @SysUISingleton
+ fun bindWallpaperRepository(impl: WallpaperRepositoryImpl): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
new file mode 100644
index 000000000000..b45b8cd15bf5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A no-op implementation of [WallpaperRepository].
+ *
+ * Used for variants of SysUI that do not support wallpaper but require other SysUI classes that
+ * have a wallpaper dependency.
+ */
+@SysUISingleton
+class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
+ override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
new file mode 100644
index 000000000000..b8f95832b852
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository storing information about the current wallpaper. */
+interface WallpaperRepository {
+ /** Emits the current user's current wallpaper. */
+ val wallpaperInfo: StateFlow<WallpaperInfo?>
+
+ /** Emits true if the current user's current wallpaper supports ambient mode. */
+ val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class WallpaperRepositoryImpl
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ broadcastDispatcher: BroadcastDispatcher,
+ userRepository: UserRepository,
+ private val wallpaperManager: WallpaperManager,
+ context: Context,
+) : WallpaperRepository {
+ private val deviceSupportsAodWallpaper =
+ context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper)
+
+ private val wallpaperChanged: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
+ user = UserHandle.ALL,
+ )
+ // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
+ // input flows emit at least once. Since this flow is an input flow, it needs to emit
+ // when it starts up to ensure that the `combine` will run if the user changes before we
+ // receive a ACTION_WALLPAPER_CHANGED intent.
+ // Note that the `selectedUser` flow does *not* need to emit on start because
+ // [UserRepository.selectedUser] is a state flow which will automatically emit a value
+ // on start.
+ .onStart { emit(Unit) }
+
+ private val selectedUser: Flow<SelectedUserModel> =
+ userRepository.selectedUser
+ // Only update the wallpaper status once the user selection has finished.
+ .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> =
+ if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
+ MutableStateFlow(null).asStateFlow()
+ } else {
+ combine(wallpaperChanged, selectedUser) { _, selectedUser ->
+ getWallpaper(selectedUser)
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ initialValue = getWallpaper(userRepository.selectedUser.value),
+ )
+ }
+
+ override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+ wallpaperInfo
+ .map {
+ // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+ it?.supportsAmbientMode() == true
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
+ )
+
+ private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+ return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 319a02d911ed..d506584cdfac 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -33,12 +33,15 @@ import android.app.admin.IKeyguardCallback;
import android.app.admin.IKeyguardClient;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
+import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import android.view.View;
@@ -50,7 +53,6 @@ import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -60,7 +62,6 @@ import org.mockito.MockitoAnnotations;
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@Ignore("b/286245842")
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
@@ -79,7 +80,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private KeyguardSecurityCallback mKeyguardCallback;
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
- @Mock
+
private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@Before
@@ -99,6 +100,11 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
+ Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+ Display.DEFAULT_DISPLAY);
+ mSurfacePackage = (new SurfaceControlViewHost(mContext, display,
+ new Binder())).getSurfacePackage();
+
mTestController = new AdminSecondaryLockScreenController.Factory(
mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
.create(mKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 5d75428b8fb4..cb182297eae1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -76,7 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
@@ -119,7 +119,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.onViewAttached()
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
@@ -131,15 +131,20 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(mPostureController).addCallback(postureCallbackCaptor.capture())
val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
- // Verify view is now in posture state DEVICE_POSTURE_OPENED
- assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
+ // Simulate posture change to same state with callback
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
}
private fun getPatternTopGuideline(): Float {
@@ -150,7 +155,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
}
- private fun getExpectedTopGuideline(): Float {
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d256ee163877..4dc7652f83cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -19,6 +19,8 @@ package com.android.keyguard
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@ import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +55,10 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
- @Mock private lateinit var keyguardPinView: KeyguardPINView
+
+ private lateinit var objectKeyguardPINView: KeyguardPINView
+
+ @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
@Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
@@ -83,64 +90,73 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock lateinit var deleteButton: NumPadButton
@Mock lateinit var enterButton: View
- private lateinit var pinViewController: KeyguardPinViewController
-
@Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+ Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
.thenReturn(keyguardMessageArea)
Mockito.`when`(
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
- `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
- `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+ `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+ `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
.thenReturn(passwordTextView)
- `when`(keyguardPinView.resources).thenReturn(context.resources)
- `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+ `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+ `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
- `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+ `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
// For posture tests:
- `when`(keyguardPinView.buttons).thenReturn(arrayOf())
+ `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
- pinViewController =
- KeyguardPinViewController(
- keyguardPinView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- mKeyguardSecurityCallback,
- keyguardMessageAreaControllerFactory,
- mLatencyTracker,
- liftToActivateListener,
- mEmergencyButtonController,
- falsingCollector,
- postureController,
- featureFlags
- )
+ objectKeyguardPINView =
+ View.inflate(mContext, R.layout.keyguard_pin_view, null)
+ .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+ }
+
+ private fun constructPinViewController(
+ mKeyguardPinView: KeyguardPINView
+ ): KeyguardPinViewController {
+ return KeyguardPinViewController(
+ mKeyguardPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ mKeyguardSecurityCallback,
+ keyguardMessageAreaControllerFactory,
+ mLatencyTracker,
+ liftToActivateListener,
+ mEmergencyButtonController,
+ falsingCollector,
+ postureController,
+ featureFlags
+ )
}
@Test
- fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
- fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
- // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(postureController).addCallback(postureCallbackCaptor.capture())
@@ -148,31 +164,57 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is now in posture state DEVICE_POSTURE_OPENED
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ // Simulate posture change to same state with callback
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+ }
+
+ private fun getPinTopGuideline(): Float {
+ val cs = ConstraintSet()
+ val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+ cs.clone(container)
+ return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+ }
+
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
+ return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
@Test
fun startAppearAnimation() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -181,12 +223,15 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -195,7 +240,10 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun handleLockout_readsNumberOfErrorAttempts() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.handleAttemptLockout(0)
+
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index da9ceb47446a..212dad78d5b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.children
import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
- fun animatesRootAndChildren() {
+ fun animatesRootAndChildren_withoutExcludedViews() {
setUpRootWithChildren()
val success = ViewHierarchyAnimator.animate(rootView)
@@ -208,6 +208,40 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
+ fun animatesRootAndChildren_withExcludedViews() {
+ setUpRootWithChildren()
+
+ val success = ViewHierarchyAnimator.animate(
+ rootView,
+ excludedViews = setOf(rootView.getChildAt(0))
+ )
+ // Change all bounds.
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ )
+ rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The initial values for the affected views should be those of the previous layout, while
+ // the excluded view should be at the final values from the beginning.
+ checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
+ }
+
+ @Test
fun animatesInvisibleViews() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
rootView.visibility = View.INVISIBLE
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 c0c690892958..a6ad4b24b63d 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
@@ -103,19 +103,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
}
@Test
- fun toggleBypassEnabled() =
- testScope.runTest {
- val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
- assertThat(isBypassEnabled).isFalse()
-
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isTrue()
-
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isFalse()
- }
-
- @Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
utils.authenticationRepository.setUnlocked(false)
@@ -164,7 +151,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(isThrottled).isFalse()
}
@@ -172,7 +159,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun authenticate_withIncorrectPin_returnsFalse() =
testScope.runTest {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
+ assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
}
@Test(expected = IllegalArgumentException::class)
@@ -270,7 +257,15 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
+ removeLast()
+ },
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
assertThat(isThrottled).isFalse()
}
@@ -280,7 +275,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
+ tryAutoConfirm = true
+ )
+ )
+ .isFalse()
assertThat(isUnlocked).isFalse()
}
@@ -290,7 +291,12 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
+ tryAutoConfirm = true
+ )
+ )
.isFalse()
assertThat(isUnlocked).isFalse()
}
@@ -301,7 +307,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isTrue()
assertThat(isUnlocked).isTrue()
}
@@ -311,7 +323,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val isUnlocked by collectLastValue(underTest.isUnlocked)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(false)
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
assertThat(isUnlocked).isFalse()
}
@@ -334,7 +352,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val throttling by collectLastValue(underTest.throttling)
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- underTest.authenticate(listOf(1, 2, 3, 4))
+ underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
assertThat(isUnlocked).isTrue()
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
@@ -366,7 +384,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
// Correct PIN, but throttled, so doesn't attempt it:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
assertThat(isUnlocked).isFalse()
assertThat(isThrottled).isTrue()
assertThat(throttling)
@@ -412,9 +430,62 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
// Correct PIN and no longer throttled so unlocks successfully:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(isUnlocked).isTrue()
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
}
+
+ @Test
+ fun hintedPinLength_withoutAutoConfirm_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(false)
+
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ }
+ )
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.overrideCredential(
+ buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
+ )
+
+ assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+ }
+
+ @Test
+ fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
+ testScope.runTest {
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ }
+ )
+ utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+ assertThat(hintedPinLength).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index d022653a33c8..ecc776b98c6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -44,25 +44,27 @@ import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
import android.view.WindowMetrics
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -99,7 +101,7 @@ private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsControllerTest : SysuiTestCase() {
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -118,6 +120,8 @@ class SideFpsControllerTest : SysuiTestCase() {
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private lateinit var displayStateInteractor: DisplayStateInteractor
+ private lateinit var sideFpsOverlayViewModel: SideFpsOverlayViewModel
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val executor = FakeExecutor(FakeSystemClock())
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@@ -149,8 +153,8 @@ class SideFpsControllerTest : SysuiTestCase() {
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
FakeBiometricSettingsRepository(),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ mock(KeyguardUpdateMonitor::class.java),
)
displayStateInteractor =
DisplayStateInteractorImpl(
@@ -159,6 +163,15 @@ class SideFpsControllerTest : SysuiTestCase() {
executor,
rearDisplayStateRepository
)
+ sideFpsOverlayViewModel =
+ SideFpsOverlayViewModel(context, SideFpsOverlayInteractorImpl(fingerprintRepository))
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations = mapOf("" to SensorLocationInternal("", 2500, 0, 0))
+ )
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -265,6 +278,7 @@ class SideFpsControllerTest : SysuiTestCase() {
executor,
handler,
alternateBouncerInteractor,
+ { sideFpsOverlayViewModel },
TestCoroutineScope(),
dumpManager
)
@@ -683,106 +697,6 @@ class SideFpsControllerTest : SysuiTestCase() {
verify(windowManager).removeView(any())
}
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
@Test
fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
// By default all those tests assume the side fps sensor is available.
@@ -795,51 +709,6 @@ class SideFpsControllerTest : SysuiTestCase() {
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
-
- @Test
- fun testLayoutParams_isKeyguardDialogType() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpType = overlayViewParamsCaptor.value.type
-
- assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasTrustedOverlayWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
- }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 9df06dc9e18f..8dfeb3bde0c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -34,7 +34,6 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -106,8 +105,8 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
mock(BiometricSettingsRepository::class.java),
- mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
+ mKeyguardUpdateMonitor,
)
return createUdfpsKeyguardViewController(
/* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317b92f5..ea2561594793 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -73,10 +73,15 @@ class FingerprintRepositoryImplTest : SysuiTestCase() {
@Test
fun initializeProperties() =
testScope.runTest {
- val isInitialized = collectLastValue(repository.isInitialized)
+ val sensorId by collectLastValue(repository.sensorId)
+ val strength by collectLastValue(repository.strength)
+ val sensorType by collectLastValue(repository.sensorType)
+ val sensorLocations by collectLastValue(repository.sensorLocations)
- assertDefaultProperties()
- assertThat(isInitialized()).isFalse()
+ // Assert default properties.
+ assertThat(sensorId).isEqualTo(-1)
+ assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
val fingerprintProps =
listOf(
@@ -115,31 +120,24 @@ class FingerprintRepositoryImplTest : SysuiTestCase() {
fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
- assertThat(repository.sensorId.value).isEqualTo(1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+ assertThat(sensorId).isEqualTo(1)
+ assertThat(strength).isEqualTo(SensorStrength.STRONG)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
- assertThat(repository.sensorLocations.value.size).isEqualTo(2)
- assertThat(repository.sensorLocations.value).containsKey("display_id_1")
- with(repository.sensorLocations.value["display_id_1"]!!) {
+ assertThat(sensorLocations?.size).isEqualTo(2)
+ assertThat(sensorLocations).containsKey("display_id_1")
+ with(sensorLocations?.get("display_id_1")!!) {
assertThat(displayId).isEqualTo("display_id_1")
assertThat(sensorLocationX).isEqualTo(100)
assertThat(sensorLocationY).isEqualTo(300)
assertThat(sensorRadius).isEqualTo(20)
}
- assertThat(repository.sensorLocations.value).containsKey("")
- with(repository.sensorLocations.value[""]!!) {
+ assertThat(sensorLocations).containsKey("")
+ with(sensorLocations?.get("")!!) {
assertThat(displayId).isEqualTo("")
assertThat(sensorLocationX).isEqualTo(540)
assertThat(sensorLocationY).isEqualTo(1636)
assertThat(sensorRadius).isEqualTo(130)
}
- assertThat(isInitialized()).isTrue()
}
-
- private fun assertDefaultProperties() {
- assertThat(repository.sensorId.value).isEqualTo(-1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf45504b..896f9b114679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -51,8 +52,9 @@ class SideFpsOverlayInteractorTest : SysuiTestCase() {
}
@Test
- fun testGetOverlayOffsets() =
+ fun testGetOverlayoffsets() =
testScope.runTest {
+ // Arrange.
fingerprintRepository.setProperties(
sensorId = 1,
strength = SensorStrength.STRONG,
@@ -76,16 +78,33 @@ class SideFpsOverlayInteractorTest : SysuiTestCase() {
)
)
- var offsets = interactor.getOverlayOffsets("display_id_1")
- assertThat(offsets.displayId).isEqualTo("display_id_1")
- assertThat(offsets.sensorLocationX).isEqualTo(100)
- assertThat(offsets.sensorLocationY).isEqualTo(300)
- assertThat(offsets.sensorRadius).isEqualTo(20)
+ // Act.
+ val offsets by collectLastValue(interactor.overlayOffsets)
+ val displayId by collectLastValue(interactor.displayId)
- offsets = interactor.getOverlayOffsets("invalid_display_id")
- assertThat(offsets.displayId).isEqualTo("")
- assertThat(offsets.sensorLocationX).isEqualTo(540)
- assertThat(offsets.sensorLocationY).isEqualTo(1636)
- assertThat(offsets.sensorRadius).isEqualTo(130)
+ // Assert offsets of empty displayId.
+ assertThat(displayId).isEqualTo("")
+ assertThat(offsets?.displayId).isEqualTo("")
+ assertThat(offsets?.sensorLocationX).isEqualTo(540)
+ assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+ assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+ // Offsets should be updated correctly.
+ interactor.changeDisplay("display_id_1")
+ assertThat(displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.sensorLocationX).isEqualTo(100)
+ assertThat(offsets?.sensorLocationY).isEqualTo(300)
+ assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+ // Should return default offset when the displayId is invalid.
+ interactor.changeDisplay("invalid_display_id")
+ assertThat(displayId).isEqualTo("invalid_display_id")
+ assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+ assertThat(offsets?.sensorLocationX)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+ assertThat(offsets?.sensorLocationY)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+ assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
new file mode 100644
index 000000000000..a8593216e22a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayViewModelTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private var testScope: TestScope = TestScope(StandardTestDispatcher())
+
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
+ private lateinit var interactor: SideFpsOverlayInteractor
+ private lateinit var viewModel: SideFpsOverlayViewModel
+
+ enum class DeviceConfig {
+ X_ALIGNED,
+ Y_ALIGNED,
+ }
+
+ private lateinit var deviceConfig: DeviceConfig
+ private lateinit var indicatorBounds: Rect
+ private lateinit var displayBounds: Rect
+ private lateinit var sensorLocation: SensorLocationInternal
+ private var displayWidth: Int = 0
+ private var displayHeight: Int = 0
+ private var boundsWidth: Int = 0
+ private var boundsHeight: Int = 0
+
+ @Before
+ fun setup() {
+ interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations =
+ mapOf(
+ "" to
+ SensorLocationInternal(
+ "" /* displayId */,
+ 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */
+ ),
+ "display_id_1" to
+ SensorLocationInternal(
+ "display_id_1" /* displayId */,
+ 100 /* sensorLocationX */,
+ 300 /* sensorLocationY */,
+ 20 /* sensorRadius */
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testOverlayOffsets() =
+ testScope.runTest {
+ viewModel = SideFpsOverlayViewModel(mContext, interactor)
+
+ val interactorOffsets by collectLastValue(interactor.overlayOffsets)
+ val viewModelOffsets by collectLastValue(viewModel.overlayOffsets)
+
+ assertThat(viewModelOffsets).isEqualTo(interactorOffsets)
+ }
+
+ private fun testWithDisplay(
+ deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation: Boolean = false,
+ initInfo: DisplayInfo.() -> Unit = {},
+ block: () -> Unit
+ ) {
+ this.deviceConfig = deviceConfig
+
+ when (deviceConfig) {
+ DeviceConfig.X_ALIGNED -> {
+ displayWidth = 3000
+ displayHeight = 1500
+ sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+ boundsWidth = 200
+ boundsHeight = 100
+ }
+ DeviceConfig.Y_ALIGNED -> {
+ displayWidth = 2500
+ displayHeight = 2000
+ sensorLocation = SensorLocationInternal("", 0, 300, 0)
+ boundsWidth = 100
+ boundsHeight = 200
+ }
+ }
+
+ indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+ displayBounds = Rect(0, 0, displayWidth, displayHeight)
+
+ val displayInfo = DisplayInfo()
+ displayInfo.initInfo()
+
+ val dmGlobal = Mockito.mock(DisplayManagerGlobal::class.java)
+ val display =
+ Display(
+ dmGlobal,
+ DISPLAY_ID,
+ displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+
+ whenever(dmGlobal.getDisplayInfo(ArgumentMatchers.eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+ val sideFpsOverlayViewModelContext =
+ context.createDisplayContext(display) as SysuiTestableContext
+ sideFpsOverlayViewModelContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_reverseDefaultRotation,
+ isReverseDefaultRotation
+ )
+ viewModel = SideFpsOverlayViewModel(sideFpsOverlayViewModelContext, interactor)
+
+ block()
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+ assertThat(displayInfo.rotation).isEqualTo(Surface.ROTATION_0)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 37b9ca49ef57..186df02536ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.bouncer.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -54,6 +55,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Before
fun setup() {
@@ -72,8 +74,8 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
keyguardStateController,
bouncerRepository,
biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository,
systemClock,
+ keyguardUpdateMonitor,
)
}
@@ -118,7 +120,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
@Test
fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
givenCanShowAlternateBouncer()
- deviceEntryFingerprintAuthRepository.setLockedOut(true)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -168,7 +170,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setStrongBiometricAllowed(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
- deviceEntryFingerprintAuthRepository.setLockedOut(false)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
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 481f36e8ea38..6babf0490ea9 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
@@ -94,7 +94,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -118,13 +118,20 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- // Wrong 4-digit pin
- assertThat(underTest.authenticate(listOf(1, 2, 3, 5), tryAutoConfirm = true)).isFalse()
+ // Wrong 6-digit pin
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
+ .isFalse()
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -147,7 +154,13 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Correct input.
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
+ assertThat(
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+ )
+ .isNull()
assertThat(message).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@@ -309,7 +322,7 @@ class BouncerInteractorTest : SysuiTestCase() {
)
// Correct PIN, but throttled, so doesn't change away from the bouncer scene:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
assertTryAgainMessage(
message,
@@ -339,7 +352,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
// Correct PIN and no longer throttled so changes to the Gone scene:
- assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
assertThat(isThrottled).isFalse()
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
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 0867558545bd..2cc949326fa0 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
@@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
@@ -60,10 +61,9 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
assertThat(animateFailure).isFalse()
// Wrong PIN:
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
- underTest.onPinButtonClicked(5)
- underTest.onPinButtonClicked(6)
+ FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isTrue()
@@ -71,10 +71,9 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
assertThat(animateFailure).isFalse()
// Correct PIN:
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isFalse()
}
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 5c6d4c69b50b..45d1af722369 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
@@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
@@ -211,10 +212,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
underTest.onAuthenticateButtonClicked()
@@ -275,10 +275,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct PIN:
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
assertThat(message?.text).isEmpty()
underTest.onAuthenticateButtonClicked()
@@ -300,10 +299,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(4)
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -324,10 +322,12 @@ class PinBouncerViewModelTest : SysuiTestCase() {
)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
- underTest.onPinButtonClicked(1)
- underTest.onPinButtonClicked(2)
- underTest.onPinButtonClicked(3)
- underTest.onPinButtonClicked(5) // PIN is now wrong!
+ FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onPinButtonClicked(
+ FakeAuthenticationRepository.DEFAULT_PIN.last() + 1
+ ) // PIN is now wrong!
assertThat(entries).hasSize(0)
assertThat(message?.text).isEqualTo(WRONG_PIN)
@@ -386,47 +386,6 @@ class PinBouncerViewModelTest : SysuiTestCase() {
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
- @Test
- fun hintedPinLength_withoutAutoConfirm_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(false)
-
- assertThat(hintedPinLength).isNull()
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
-
- assertThat(hintedPinLength).isNull()
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
- utils.authenticationRepository.overrideCredential(listOf(1, 2, 3, 4, 5, 6))
-
- assertThat(hintedPinLength).isEqualTo(6)
- }
-
- @Test
- fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() =
- testScope.runTest {
- val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
-
- assertThat(hintedPinLength).isNull()
- }
-
companion object {
private const val ENTER_YOUR_PIN = "Enter your pin"
private const val WRONG_PIN = "Wrong pin"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 666978e78f98..7379cd51d383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -71,6 +71,7 @@ import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
@@ -108,9 +109,11 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import org.junit.After;
@@ -124,6 +127,7 @@ import org.mockito.MockitoAnnotations;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -131,6 +135,9 @@ import kotlinx.coroutines.flow.Flow;
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
@@ -182,6 +189,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
private FakeSystemClock mSystemClock;
+ private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
private @Mock CoroutineDispatcher mDispatcher;
private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
@@ -817,6 +825,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKeyguardTransitions,
mInteractionJankMonitor,
mDreamOverlayStateController,
+ mJavaAdapter,
+ mWallpaperRepository,
() -> mShadeController,
() -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 020c0b22ba27..47c662c3b4b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
@@ -50,12 +51,12 @@ import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -113,6 +114,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var sessionTracker: SessionTracker
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -131,8 +133,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var testScope: TestScope
private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var authStatus: FlowValue<AuthenticationStatus?>
- private lateinit var detectStatus: FlowValue<DetectionStatus?>
+ private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
+ private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
@@ -175,10 +177,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
AlternateBouncerInteractor(
bouncerRepository = bouncerRepository,
biometricSettingsRepository = biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
systemClock = mock(SystemClock::class.java),
keyguardStateController = FakeKeyguardStateController(),
statusBarStateController = mock(StatusBarStateController::class.java),
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
)
bypassStateChangedListener =
@@ -262,7 +264,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
val successResult = successResult()
authenticationCallback.value.onAuthenticationSucceeded(successResult)
- assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+ assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult))
assertThat(authenticated()).isTrue()
assertThat(authRunning()).isFalse()
assertThat(canFaceAuthRun()).isFalse()
@@ -383,7 +385,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
detectionCallback.value.onFaceDetected(1, 1, true)
- assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+ assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
}
@Test
@@ -423,7 +425,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
FACE_ERROR_CANCELED,
"First auth attempt cancellation completed"
)
- val value = authStatus() as ErrorAuthenticationStatus
+ val value = authStatus() as ErrorFaceAuthenticationStatus
assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
@@ -465,7 +467,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
- assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+ assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg"))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 264328b04227..def016ad8381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -26,7 +26,11 @@ import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +53,6 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var authController: AuthController
@Captor
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -68,7 +71,6 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
authController,
keyguardUpdateMonitor,
testScope.backgroundScope,
- dumpManager,
)
}
@@ -177,4 +179,129 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
}
+
+ @Test
+ fun onFingerprintSuccess_successAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FINGERPRINT,
+ true,
+ )
+
+ val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
+ assertThat(status.userId).isEqualTo(0)
+ assertThat(status.isStrongBiometric).isEqualTo(true)
+ }
+
+ @Test
+ fun onFingerprintFailed_failedAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ assertThat(authenticationStatus)
+ .isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
+ }
+
+ @Test
+ fun onFingerprintError_errorAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintHelp_helpAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as HelpFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintAcquired_acquiredAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FINGERPRINT,
+ 5,
+ )
+
+ val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
+ assertThat(status.acquiredInfo).isEqualTo(5)
+ }
+
+ @Test
+ fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FACE,
+ true,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ 5,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+ }
}
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 e9f0d561371c..ba7d3490e56d 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
@@ -31,16 +31,20 @@ import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
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.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -70,9 +74,11 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var screenLifecycle: ScreenLifecycle
@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
@@ -90,8 +96,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
KeyguardRepositoryImpl(
statusBarStateController,
wakefulnessLifecycle,
+ screenLifecycle,
biometricUnlockController,
keyguardStateController,
+ keyguardBypassController,
keyguardUpdateMonitor,
dozeTransitionListener,
dozeParameters,
@@ -157,6 +165,16 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun dozeTimeTick() =
+ testScope.runTest {
+ var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
+ underTest.dozeTimeTick()
+ runCurrent()
+
+ assertThat(dozeTimeTickValue()).isNull()
+ }
+
+ @Test
fun isKeyguardShowing() =
testScope.runTest {
whenever(keyguardStateController.isShowing).thenReturn(false)
@@ -186,6 +204,20 @@ 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)
@@ -354,6 +386,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun screenModel() =
+ testScope.runTest {
+ val values = mutableListOf<ScreenModel>()
+ val job = underTest.screenModel.onEach(values::add).launchIn(this)
+
+ runCurrent()
+ val captor = argumentCaptor<ScreenLifecycle.Observer>()
+ verify(screenLifecycle).addObserver(captor.capture())
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
+ captor.value.onScreenTurningOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
+ captor.value.onScreenTurnedOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState())
+ .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
+ captor.value.onScreenTurningOff()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
+ captor.value.onScreenTurnedOff()
+ runCurrent()
+
+ assertThat(values.map { it.state })
+ .isEqualTo(
+ listOf(
+ // Initial value will be OFF
+ ScreenState.SCREEN_OFF,
+ ScreenState.SCREEN_TURNING_ON,
+ ScreenState.SCREEN_ON,
+ ScreenState.SCREEN_TURNING_OFF,
+ ScreenState.SCREEN_OFF,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun isUdfpsSupported() =
testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
new file mode 100644
index 000000000000..3389fa9a48af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BiometricMessageInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: BiometricMessageInteractor
+ private lateinit var testScope: TestScope
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+
+ @Mock private lateinit var indicationHelper: IndicationHelper
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+ underTest =
+ BiometricMessageInteractor(
+ mContext.resources,
+ fingerprintAuthRepository,
+ fingerprintPropertyRepository,
+ indicationHelper,
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun fingerprintErrorMessage() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+ // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
+ whenever(
+ indicationHelper.shouldSuppressErrorMsg(
+ FINGERPRINT,
+ FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ )
+ )
+ .thenReturn(false)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage is updated
+ assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
+ assertThat(fingerprintErrorMessage?.id)
+ .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
+ assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintErrorMessage_suppressedError() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+ // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
+ whenever(
+ indicationHelper.shouldSuppressErrorMsg(
+ FINGERPRINT,
+ FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ )
+ )
+ .thenReturn(true)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage isn't update - it's still null
+ assertThat(fingerprintErrorMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintHelpMessage() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage is updated
+ assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
+ assertThat(fingerprintHelpMessage?.id)
+ .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
+ assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintHelpMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+ // GIVEN primary auth is required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage isn't update - it's still null
+ assertThat(fingerprintHelpMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintFailMessage_nonUdfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // GIVEN rear fingerprint (not UDFPS)
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.REAR,
+ mapOf()
+ )
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated
+ assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+ assertThat(fingerprintFailMessage?.id)
+ .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ mContext.resources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun fingerprintFailMessage_udfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // GIVEN UDFPS
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ mapOf()
+ )
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated to udfps message
+ assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+ assertThat(fingerprintFailMessage?.id)
+ .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ mContext.resources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun fingerprintFailedMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false)
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailedMessage isn't update - it's still null
+ assertThat(fingerprintFailedMessage).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 3e81cd336824..ced0a213ca97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -38,10 +38,9 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -121,8 +120,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
mock(KeyguardStateController::class.java),
bouncerRepository,
mock(BiometricSettingsRepository::class.java),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ keyguardUpdateMonitor,
),
keyguardTransitionInteractor,
featureFlags,
@@ -160,7 +159,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
underTest.onDeviceLifted()
- val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus
+ val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus
assertThat(outputValue.msgId)
.isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b4b307301138..9a90a5ceb259 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -216,9 +216,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var recCardTitle: TextView
@Mock private lateinit var coverItem: ImageView
@Mock private lateinit var matrix: Matrix
- private lateinit var coverItem1: ImageView
- private lateinit var coverItem2: ImageView
- private lateinit var coverItem3: ImageView
private lateinit var recTitle1: TextView
private lateinit var recTitle2: TextView
private lateinit var recTitle3: TextView
@@ -233,7 +230,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
FakeFeatureFlags().apply {
this.set(Flags.UMO_SURFACE_RIPPLE, false)
this.set(Flags.UMO_TURBULENCE_NOISE, false)
- this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
@@ -467,21 +463,25 @@ public class MediaControlPanelTest : SysuiTestCase() {
recSubtitle3 = TextView(context)
whenever(recommendationViewHolder.recommendations).thenReturn(view)
- whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon)
-
- // Add a recommendation item
- coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) }
- coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) }
- coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) }
-
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem1, coverItem2, coverItem3))
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
whenever(recommendationViewHolder.mediaCoverContainers)
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
whenever(recommendationViewHolder.mediaTitles)
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
whenever(recommendationViewHolder.mediaSubtitles)
.thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+ // set ids for recommendation containers
+ whenever(coverContainer1.id).thenReturn(1)
+ whenever(coverContainer2.id).thenReturn(2)
+ whenever(coverContainer3.id).thenReturn(3)
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
@@ -1561,7 +1561,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -1585,7 +1586,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -2151,7 +2153,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendation_setAfterExecutors() {
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2189,7 +2190,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendationWithProgressBars() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val bundle =
Bundle().apply {
@@ -2236,7 +2236,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2290,7 +2289,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2505,27 +2503,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
}
- private fun setupUpdatedRecommendationViewHolder() {
- fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverContainers)
- .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- // set ids for recommendation containers
- whenever(coverContainer1.id).thenReturn(1)
- whenever(coverContainer2.id).thenReturn(2)
- whenever(coverContainer3.id).thenReturn(3)
- }
-
private fun getColorIcon(color: Int): Icon {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index c9956f36dbeb..ba97df910e43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -201,8 +201,8 @@ class MediaViewControllerTest : SysuiTestCase() {
whenever(mockCopiedState.widgetStates)
.thenReturn(
mutableMapOf(
- R.id.media_title1 to mediaTitleWidgetState,
- R.id.media_subtitle1 to mediaSubTitleWidgetState,
+ R.id.media_title to mediaTitleWidgetState,
+ R.id.media_subtitle to mediaSubTitleWidgetState,
R.id.media_cover1_container to mediaContainerWidgetState
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
new file mode 100644
index 000000000000..45f0a8c62125
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.ContentRecordingSession
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeMediaProjectionManager {
+
+ val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
+
+ init {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callbacks += it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ whenever(mediaProjectionManager.removeCallback(any())).thenAnswer {
+ callbacks -= it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ }
+
+ fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStart(info) }
+ }
+
+ fun dispatchOnStop(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStop(info) }
+ }
+
+ fun dispatchOnSessionSet(
+ info: MediaProjectionInfo = DEFAULT_INFO,
+ session: ContentRecordingSession?
+ ) {
+ callbacks.forEach { it.onRecordingSessionSet(info, session) }
+ }
+
+ companion object {
+ fun createDisplaySession(): ContentRecordingSession =
+ ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+ fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
+ ContentRecordingSession.createTaskSession(token)
+
+ private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
+ private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
+ private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
deleted file mode 100644
index c59fd60cca9b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
-
-import android.app.TaskInfo
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeMediaProjectionRepository : MediaProjectionRepository {
-
- private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
-
- fun switchProjectedTask(newTask: TaskInfo) {
- state.value = MediaProjectionState.SingleTask(newTask)
- }
-
- override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
-
- fun projectEntireScreen() {
- state.value = MediaProjectionState.EntireScreen
- }
-
- fun stopProjecting() {
- state.value = MediaProjectionState.NotProjecting
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
deleted file mode 100644
index 593e3893fb2a..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.content.Intent
-import android.os.IBinder
-import android.window.IWindowContainerToken
-import android.window.WindowContainerToken
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeTasksRepository : TasksRepository {
-
- private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
-
- override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
-
- private val runningTasks = mutableListOf(DEFAULT_TASK)
-
- override suspend fun findRunningTaskFromWindowContainerToken(
- windowContainerToken: IBinder
- ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
-
- fun addRunningTask(task: RunningTaskInfo) {
- runningTasks.add(task)
- }
-
- fun moveTaskToForeground(task: RunningTaskInfo) {
- _foregroundTask.value = task
- }
-
- companion object {
- val DEFAULT_TASK = createTask(taskId = -1)
- val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
-
- fun createTask(
- taskId: Int,
- token: WindowContainerToken = createToken(),
- baseIntent: Intent = Intent()
- ) =
- RunningTaskInfo().apply {
- this.taskId = taskId
- this.token = token
- this.baseIntent = baseIntent
- }
-
- fun createToken(): WindowContainerToken {
- val realToken = object : IWindowContainerToken.Stub() {}
- return WindowContainerToken(realToken)
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 2b074655bb02..3a74c7255cbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -16,27 +16,22 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
-import android.media.projection.MediaProjectionInfo
-import android.media.projection.MediaProjectionManager
import android.os.Binder
import android.os.Handler
-import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,29 +40,26 @@ import org.junit.runner.RunWith
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val mediaProjectionManager = mock<MediaProjectionManager>()
-
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private val tasksRepo = FakeTasksRepository()
- private lateinit var callback: MediaProjectionManager.Callback
- private lateinit var repo: MediaProjectionManagerRepository
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
- @Before
- fun setUp() {
- whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
- callback = it.arguments[0] as MediaProjectionManager.Callback
- return@thenAnswer Unit
- }
- repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo
- )
- }
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
@Test
fun mediaProjectionState_onStart_emitsNotProjecting() =
@@ -75,7 +67,7 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStart(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStart()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -86,7 +78,7 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStop(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStop()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -97,7 +89,7 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -108,8 +100,9 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -120,9 +113,10 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session =
- ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -134,8 +128,9 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
runCurrent()
val taskWindowContainerToken = Binder()
- val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -143,20 +138,16 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
@Test
fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
testScope.runTest {
- val token = FakeTasksRepository.createToken()
- val task = FakeTasksRepository.createTask(taskId = 1, token = token)
- tasksRepo.addRunningTask(task)
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+ fakeActivityTaskManager.addRunningTasks(task)
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createTaskSession(token.asBinder())
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(token.asBinder())
+ )
assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
}
-
- companion object {
- val TEST_MEDIA_INFO =
- MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index 112950b860e8..b2ebe1bcbc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,7 +46,8 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
@@ -51,15 +55,26 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
backgroundDispatcher = dispatcher
)
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -67,10 +82,15 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
@Test
fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = FakeMediaProjectionManager.createDisplaySession()
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -80,9 +100,12 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(token = projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState)
@@ -99,9 +122,12 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
@@ -111,11 +137,13 @@ class TaskSwitchInteractorTest : SysuiTestCase() {
fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
testScope.runTest {
val projectedTask = createTask(taskId = 0)
- val foregroundTask = createTask(taskId = 0)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index cfbbf768e1fe..b396cafb28d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,13 +18,15 @@ package com.android.systemui.mediaprojection.taskswitcher.ui
import android.app.Notification
import android.app.NotificationManager
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
import com.android.systemui.util.mockito.any
@@ -51,14 +53,25 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private val dispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(dispatcher)
+
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
applicationScope = testScope.backgroundScope,
backgroundDispatcher = dispatcher
)
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
private val viewModel = TaskSwitcherNotificationViewModel(interactor)
@@ -90,7 +103,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
@Test
fun hideNotification() {
testScope.runTest {
- mediaRepo.stopProjecting()
+ fakeMediaProjectionManager.dispatchOnStop()
verify(notificationManager).cancel(any())
}
@@ -99,7 +112,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
@Test
fun notificationIdIsConsistent() {
testScope.runTest {
- mediaRepo.stopProjecting()
+ fakeMediaProjectionManager.dispatchOnStop()
val idCancel = argumentCaptor<Int>()
verify(notificationManager).cancel(idCancel.capture())
@@ -114,7 +127,11 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private fun switchTask() {
val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
- mediaRepo.switchProjectedTask(projectedTask)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index ea44fb3b1f6e..7d38de4bc2d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,10 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import com.google.common.truth.Truth.assertThat
@@ -44,13 +48,23 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
applicationScope = testScope.backgroundScope,
backgroundDispatcher = dispatcher
)
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
private val viewModel = TaskSwitcherNotificationViewModel(interactor)
@@ -58,19 +72,23 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
@Test
fun uiState_notProjecting_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnStop()
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -78,19 +96,23 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
@Test
fun uiState_projectingEntireScreen_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -100,9 +122,12 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState)
@@ -113,9 +138,12 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
@@ -126,9 +154,12 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 45bb9313264c..435a1f1327d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -182,6 +182,32 @@ class PowerInteractorTest : SysuiTestCase() {
assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
}
+ @Test
+ fun wakeUpIfDreaming_dreaming_woken() {
+ // GIVEN device is dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+ // THEN device is woken up
+ assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+ }
+
+ @Test
+ fun wakeUpIfDreaming_notDreaming_notWoken() {
+ // GIVEN device is not dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP)
+
+ // THEN device is not woken
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a0d8f98a4ad1..9d9d0c7de2ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -154,6 +154,21 @@ class QSPanelControllerTest : SysuiTestCase() {
verify(qsPanel).setCanCollapse(true)
}
+ @Test
+ fun multipleListeningOnlyCallsBrightnessControllerOnce() {
+ controller.setListening(true, true)
+ controller.setListening(true, false)
+ controller.setListening(true, true)
+
+ verify(brightnessController).registerCallbacks()
+
+ controller.setListening(false, true)
+ controller.setListening(false, false)
+ controller.setListening(false, true)
+
+ verify(brightnessController).unregisterCallbacks()
+ }
+
private fun setShouldUseSplitShade(shouldUse: Boolean) {
testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
index 3e9ddcb06389..5638d708cf30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.scene.shared.model.SceneContainerNames
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.flow.map
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -36,7 +35,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
@@ -385,7 +383,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
) {
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
- authenticationRepository.setBypassEnabled(isBypassEnabled)
+ keyguardRepository.setBypassEnabled(isBypassEnabled)
initialSceneKey?.let {
sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
new file mode 100644
index 000000000000..2b7840533df2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.service.vr.IVrManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BrightnessControllerTest : SysuiTestCase() {
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val secureSettings = FakeSettings()
+ @Mock private lateinit var toggleSlider: ToggleSlider
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var displayTracker: DisplayTracker
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var iVrManager: IVrManager
+
+ private lateinit var testableLooper: TestableLooper
+
+ private lateinit var underTest: BrightnessController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ BrightnessController(
+ context,
+ toggleSlider,
+ userTracker,
+ displayTracker,
+ displayManager,
+ secureSettings,
+ iVrManager,
+ executor,
+ mock(),
+ Handler(testableLooper.looper)
+ )
+ }
+
+ @Test
+ fun registerCallbacksMultipleTimes_onlyOneRegistration() {
+ val repeats = 100
+ repeat(repeats) { underTest.registerCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).addBrightnessChangeCallback(any(), any())
+ verify(iVrManager).registerListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+
+ @Test
+ fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() {
+ val repeats = 100
+ underTest.registerCallbacks()
+ testableLooper.processAllMessages()
+
+ repeat(repeats) { underTest.unregisterCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).removeCallback(any())
+ verify(iVrManager).unregisterListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 5c35913f6e20..ed1397ff7013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.settings.brightness
import android.content.Intent
import android.graphics.Rect
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -29,8 +28,6 @@ import androidx.test.rule.ActivityTestRule
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -53,28 +50,24 @@ import org.mockito.MockitoAnnotations
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
- @Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
+ @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
+ @Mock private lateinit var brightnessController: BrightnessController
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
private val clock = FakeSystemClock()
private val mainExecutor = FakeExecutor(clock)
- private var displayTracker = FakeDisplayTracker(mContext)
-
@Rule
@JvmField
var activityRule =
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler,
accessibilityMgr
)
},
@@ -88,6 +81,7 @@ class BrightnessDialogTest : SysuiTestCase() {
`when`(brightnessSliderControllerFactory.create(any(), any()))
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
+ `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
}
@After
@@ -178,19 +172,15 @@ class BrightnessDialogTest : SysuiTestCase() {
}
class TestDialog(
- userTracker: UserTracker,
- displayTracker: FakeDisplayTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ brightnessControllerFactory: BrightnessController.Factory,
mainExecutor: DelayableExecutor,
- backgroundHandler: Handler,
accessibilityMgr: AccessibilityManagerWrapper
) :
BrightnessDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler,
accessibilityMgr
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
new file mode 100644
index 000000000000..24d62fba8471
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.shade
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LockscreenHostedDreamGestureListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ powerRepository = FakePowerRepository()
+ keyguardRepository = FakeKeyguardRepository()
+
+ underTest =
+ LockscreenHostedDreamGestureListener(
+ falsingManager,
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ falsingCollector,
+ screenOffAnimationController,
+ statusBarStateController,
+ ),
+ statusBarStateController,
+ primaryBouncerInteractor,
+ keyguardRepository,
+ shadeLogger,
+ )
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_whileDreaming() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN wake up device if dreaming
+ Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_notOnKeyguard() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN shade is open
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_bouncerShown() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN bouncer is expanded
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_falsing() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager thinks the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the device doesn't wake up
+ Truth.assertThat(powerRepository.lastWakeWhy).isNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun testSingleTap_notDreaming_noFalsingCheck() =
+ testScope.runTest {
+ // GIVEN device not dreaming with lockscreen hosted dream
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ testScope.runCurrent()
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+}
+
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 5fb3a7955b5c..2a398c55560c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -111,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -147,6 +149,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
@@ -183,6 +186,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
mock(KeyguardMessageAreaController.Factory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 544137e95779..d9eb9b9166b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -113,6 +113,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -161,6 +163,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
val multiShadeInteractor =
@@ -196,6 +199,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 168cbb7b8da3..2bc112d68ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -1,7 +1,24 @@
+/*
+ * 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.shade
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.testing.TestableResources
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
@@ -12,6 +29,8 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
@@ -19,7 +38,10 @@ import com.android.systemui.navigationbar.NavigationModeController.ModeChangedLi
import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
@@ -40,77 +62,63 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-@SmallTest
+/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-class NotificationQSContainerControllerTest : SysuiTestCase() {
+@SmallTest
+class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
+
+ @Mock lateinit var view: NotificationsQuickSettingsContainer
+ @Mock lateinit var navigationModeController: NavigationModeController
+ @Mock lateinit var overviewProxyService: OverviewProxyService
+ @Mock lateinit var shadeHeaderController: ShadeHeaderController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
- companion object {
- const val STABLE_INSET_BOTTOM = 100
- const val CUTOUT_HEIGHT = 50
- const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
- const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
- const val NOTIFICATIONS_MARGIN = 50
- const val SCRIM_MARGIN = 10
- const val FOOTER_ACTIONS_INSET = 2
- const val FOOTER_ACTIONS_PADDING = 2
- const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
- const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
- }
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
- @Mock
- private lateinit var navigationModeController: NavigationModeController
- @Mock
- private lateinit var overviewProxyService: OverviewProxyService
- @Mock
- private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
- @Mock
- private lateinit var mShadeHeaderController: ShadeHeaderController
- @Mock
- private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock
- private lateinit var fragmentService: FragmentService
- @Mock
- private lateinit var fragmentHostManager: FragmentHostManager
- @Captor
- lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
- @Captor
- lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
- @Captor
- lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
- @Captor
- lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
- @Captor
- lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
-
- private lateinit var controller: NotificationsQSContainerController
+ lateinit var underTest: NotificationsQSContainerController
+
+ private lateinit var fakeResources: TestableResources
+ private lateinit var featureFlags: FakeFeatureFlags
private lateinit var navigationModeCallback: ModeChangedListener
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
- private lateinit var delayableExecutor: FakeExecutor
private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mContext.ensureTestableResources()
- whenever(notificationsQSContainer.context).thenReturn(mContext)
- whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
- whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
+
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
- controller = NotificationsQSContainerController(
- notificationsQSContainer,
+ underTest =
+ NotificationsQSContainerController(
+ view,
navigationModeController,
overviewProxyService,
- mShadeHeaderController,
+ shadeHeaderController,
shadeExpansionStateManager,
fragmentService,
- delayableExecutor
- )
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
@@ -118,38 +126,72 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
- .thenReturn(GESTURES_NAVIGATION)
+ .thenReturn(GESTURES_NAVIGATION)
doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
- doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
- controller.init()
- attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
navigationModeCallback = navigationModeCaptor.value
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
+ }
+
+ @Test
+ fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
}
@Test
fun testTaskbarVisibleInSplitShade() {
enableSplitShade()
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
}
@Test
@@ -157,161 +199,185 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
// when taskbar is not visible, it means we're on the home screen
enableSplitShade()
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
}
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
enableSplitShade()
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout())
- then(expectedContainerPadding = CUTOUT_HEIGHT,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
}
@Test
fun testTaskbarVisibleInSinglePaneShade() {
disableSplitShade()
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = STABLE_INSET_BOTTOM)
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedQsPadding = STABLE_INSET_BOTTOM)
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
}
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
disableSplitShade()
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets())
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
then(expectedContainerPadding = 0)
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM)
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
}
@Test
fun testDetailShowingInSinglePaneShade() {
disableSplitShade()
- controller.setDetailShowing(true)
+ underTest.setDetailShowing(true)
// always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
}
@Test
fun testDetailShowingInSplitShade() {
enableSplitShade()
- controller.setDetailShowing(true)
+ underTest.setDetailShowing(true)
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
then(expectedContainerPadding = 0)
// should not influence spacing
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
then(expectedContainerPadding = 0)
}
@Test
fun testNotificationsMarginBottomIsUpdated() {
- Mockito.clearInvocations(notificationsQSContainer)
+ Mockito.clearInvocations(view)
enableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
overrideResource(R.dimen.notification_panel_margin_bottom, 100)
disableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(100)
+ verify(view).setNotificationsMarginBottom(100)
}
@Test
fun testSplitShadeLayout_isAlignedToGuideline() {
enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(R.id.qs_edge_guideline)
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(R.id.qs_edge_guideline)
+ .isEqualTo(R.id.qs_edge_guideline)
}
@Test
fun testSinglePaneLayout_childrenHaveEqualMargins() {
disableSplitShade()
- controller.updateResources()
+ underTest.updateResources()
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
- assertThat(qsStartMargin == qsEndMargin &&
- notifStartMargin == notifEndMargin &&
- qsStartMargin == notifStartMargin
- ).isTrue()
+ assertThat(
+ qsStartMargin == qsEndMargin &&
+ notifStartMargin == notifEndMargin &&
+ qsStartMargin == notifStartMargin
+ )
+ .isTrue()
}
@Test
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
enableSplitShade()
- controller.updateResources()
+ underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(0)
+ .isEqualTo(0)
}
@Test
fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
enableSplitShade()
- controller.updateResources()
+ underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
}
@@ -322,62 +388,64 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
val largeScreenHeaderHeight = 100
overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
- controller.updateResources()
+ underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
+ .isEqualTo(largeScreenHeaderHeight)
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
+ .isEqualTo(largeScreenHeaderHeight)
}
@Test
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
setSmallScreen()
- controller.updateResources()
+ underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
}
@Test
fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
disableSplitShade()
- controller.updateResources()
- val notificationPanelMarginHorizontal = context.resources
- .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
+ .isEqualTo(notificationPanelMarginHorizontal)
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
+ .isEqualTo(notificationPanelMarginHorizontal)
}
@Test
fun testSinglePaneShadeLayout_isAlignedToParent() {
disableSplitShade()
- controller.updateResources()
+ underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID)
+ .isEqualTo(ConstraintSet.PARENT_ID)
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(ConstraintSet.PARENT_ID)
+ .isEqualTo(ConstraintSet.PARENT_ID)
}
@Test
fun testAllChildrenOfNotificationContainer_haveIds() {
// set dimen to 0 to avoid triggering updating bottom spacing
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
- val container = NotificationsQuickSettingsContainer(context, null)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
container.removeAllViews()
container.addView(newViewWithId(1))
container.addView(newViewWithId(View.NO_ID))
- val controller = NotificationsQSContainerController(
+ val controller =
+ NotificationsQSContainerController(
container,
navigationModeController,
overviewProxyService,
- mShadeHeaderController,
+ shadeHeaderController,
shadeExpansionStateManager,
fragmentService,
- delayableExecutor
- )
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
controller.updateConstraints()
assertThat(container.getChildAt(0).id).isEqualTo(1)
@@ -388,44 +456,46 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
fun testWindowInsetDebounce() {
disableSplitShade()
- given(taskbarVisible = false,
+ given(
+ taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = emptyInsets(),
- applyImmediately = false)
+ applyImmediately = false
+ )
fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
windowInsetsCallback.accept(windowInsets().withStableBottom())
delayableExecutor.advanceClockToLast()
delayableExecutor.runAllReady()
- verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
- verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
}
@Test
fun testStartCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
}
@Test
fun testEndCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 0L) // Only tracks changes
- reset(mShadeHeaderController)
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
- controller.setCustomizerShowing(false, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
}
@Test
fun testTagListenerAdded() {
- verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
}
@Test
fun testTagListenerRemoved() {
- attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
- verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
}
private fun disableSplitShade() {
@@ -438,7 +508,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
private fun setSplitShadeEnabled(enabled: Boolean) {
overrideResource(R.bool.config_use_split_notification_shade, enabled)
- controller.updateResources()
+ underTest.updateResources()
}
private fun setSmallScreen() {
@@ -459,7 +529,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
insets: WindowInsets,
applyImmediately: Boolean = true
) {
- Mockito.clearInvocations(notificationsQSContainer)
+ Mockito.clearInvocations(view)
taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
navigationModeCallback.onNavigationModeChanged(navigationMode)
windowInsetsCallback.accept(insets)
@@ -474,12 +544,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
expectedQsPadding: Int = 0
) {
- verify(notificationsQSContainer)
- .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
- verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- verify(notificationsQSContainer)
- .setQSContainerPaddingBottom(expectedQsPadding)
- Mockito.clearInvocations(notificationsQSContainer)
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
}
private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
@@ -503,10 +571,26 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
private fun newViewWithId(id: Int): View {
val view = View(mContext)
view.id = id
- val layoutParams = ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
// required as cloning ConstraintSet fails if view doesn't have layout params
view.layoutParams = layoutParams
return view
}
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index d4751c86a87f..a5048187b1b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -19,24 +19,47 @@ package com.android.systemui.shade
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,19 +74,37 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
@Mock lateinit var shadeHeaderController: ShadeHeaderController
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
lateinit var underTest: NotificationsQSContainerController
private lateinit var fakeResources: TestableResources
-
- private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- fakeResources = TestableResources(context.resources)
+ fakeSystemClock = FakeSystemClock()
+ delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
- whenever(view.resources).thenReturn(fakeResources.resources)
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
underTest =
NotificationsQSContainerController(
@@ -74,16 +115,36 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
shadeExpansionStateManager,
fragmentService,
delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
)
+
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+ overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
}
@Test
fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, false)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -94,11 +155,9 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
@Test
fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, true)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -106,4 +165,415 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
verify(view).applyConstraints(capture(captor))
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
}
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ disableSplitShade()
+ underTest.setDetailShowing(true)
+
+ // always sets spacings to 0
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ enableSplitShade()
+ underTest.setDetailShowing(true)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0)
+
+ // should not influence spacing
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ Mockito.clearInvocations(view)
+ enableSplitShade()
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
+ verify(view).setNotificationsMarginBottom(100)
+ }
+
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ underTest.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ assertThat(qsStartMargin == qsEndMargin).isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ setLargeScreen()
+ val largeScreenHeaderHeight = 100
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ }
+
+ @Test
+ fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+ setSmallScreen()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller =
+ NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+ controller.updateConstraints()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ @Test
+ fun testWindowInsetDebounce() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets(),
+ applyImmediately = false
+ )
+ fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+ windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testStartCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
+
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ underTest.updateResources()
+ }
+
+ private fun setSmallScreen() {
+ setLargeScreenEnabled(false)
+ }
+
+ private fun setLargeScreen() {
+ setLargeScreenEnabled(true)
+ }
+
+ private fun setLargeScreenEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets,
+ applyImmediately: Boolean = true
+ ) {
+ Mockito.clearInvocations(view)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ if (applyImmediately) {
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+ }
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
new file mode 100644
index 000000000000..a544cad03b77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamCoordinatorTest : SysuiTestCase() {
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notifPipeline: NotifPipeline
+ @Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
+ val testDispatcher = UnconfinedTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ private lateinit var filter: NotifFilter
+ private lateinit var statusBarListener: StatusBarStateController.StateListener
+ private lateinit var dreamCoordinator: DreamCoordinator
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // Build the coordinator
+ dreamCoordinator =
+ DreamCoordinator(
+ statusBarStateController,
+ testScope.backgroundScope,
+ keyguardRepository
+ )
+
+ // Attach the pipeline and capture the listeners/filters that it registers
+ dreamCoordinator.attach(notifPipeline)
+
+ filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
+ filter.setInvalidationListener(filterListener)
+
+ statusBarListener = withArgCaptor {
+ verify(statusBarStateController).addCallback(capture())
+ }
+ }
+
+ @Test
+ fun hideNotifications_whenDreamingAndOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on keyguard and not dreaming
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN notifications are not filtered out
+ verifyPipelinesNotInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+ // WHEN dreaming starts and the active dream is hosted in lockscreen
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications should all be filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+
+ @Test
+ fun showNotifications_whenDreamingAndNotOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN we are no longer on the keyguard
+ statusBarListener.onStateChanged(StatusBarState.SHADE)
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ @Test
+ fun showNotifications_whenOnKeyguardAndNotDreaming() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN the lockscreen hosted dream stops
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ private fun verifyPipelinesInvalidated() {
+ verify(filterListener).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun verifyPipelinesNotInvalidated() {
+ verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun clearPipelineInvocations() {
+ clearInvocations(filterListener)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index d3e5816cd1d4..daa45db6b90c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -426,23 +426,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
}
@Test
- public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
- ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
-
- NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
- entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
-
- assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
-
- verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
- verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
- }
-
- @Test
public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -455,7 +440,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
@@ -469,7 +453,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = 0L;
@@ -484,7 +467,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = -1L;
@@ -498,7 +480,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
@@ -514,7 +495,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
@@ -530,7 +510,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldNotHeadsUp_oldWhen() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
new file mode 100644
index 000000000000..d46763df8a75
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.text.PrecomputedText
+import android.text.TextPaint
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class TextPrecomputerTest : SysuiTestCase() {
+
+ private lateinit var textPrecomputer: TextPrecomputer
+
+ private lateinit var textView: TextView
+
+ @Before
+ fun before() {
+ textPrecomputer = object : TextPrecomputer {}
+ textView = TextView(mContext)
+ }
+
+ @Test
+ fun precompute_returnRunnable() {
+ // WHEN
+ val precomputeResult = textPrecomputer.precompute(textView, TEXT)
+
+ // THEN
+ assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_anyText_setPrecomputedText() {
+ // WHEN
+ textPrecomputer.precompute(textView, TEXT).run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
+ // GIVEN
+ val precomputedTextRunnable =
+ textPrecomputer.precompute(textView, TEXT, logException = false)
+
+ // WHEN
+ textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(String::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_nullText_setNull() {
+ // GIVEN
+ textView.text = TEXT
+ val precomputedTextRunnable = textPrecomputer.precompute(textView, null)
+
+ // WHEN
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isEqualTo("")
+ }
+
+ private companion object {
+ private val PAINT = TextPaint()
+ private const val TEXT = "Example Notification Test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
new file mode 100644
index 000000000000..7bbb09483b5f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerInteractorTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var underTest: SharedNotificationContainerInteractor
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ underTest =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ }
+
+ @Test
+ fun validateConfigValues() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+ overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+ overrideResource(R.dimen.notification_panel_margin_top, 10)
+ overrideResource(R.dimen.large_screen_shade_header_height, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.useSplitShade).isTrue()
+ assertThat(lastDimens.useLargeScreenHeader).isFalse()
+ assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+ assertThat(lastDimens.marginBottom).isGreaterThan(0)
+ assertThat(lastDimens.marginTop).isGreaterThan(0)
+ assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
new file mode 100644
index 000000000000..afd995460151
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var sharedNotificationContainerInteractor:
+ SharedNotificationContainerInteractor
+ private lateinit var underTest: SharedNotificationContainerViewModel
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ sharedNotificationContainerInteractor =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
+ }
+
+ @Test
+ fun validateMarginStartInSplitShade() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(0)
+ }
+
+ @Test
+ fun validateMarginStart() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(20)
+ }
+
+ @Test
+ fun validateMarginEnd() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginEnd).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginBottom() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_bottom, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginBottom).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTopWithLargeScreenHeader() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTop() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 89f8bdbfe05b..479803e1dfac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -142,7 +142,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
- mSystemClock
+ mSystemClock,
+ mStatusBarKeyguardViewManager
);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -464,6 +465,69 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void onSideFingerprintSuccess_dreaming_unlockThenWake() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+ // Verify that the power manager has been told to wake up now.
+ verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ @Test
+ public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ when(mUpdateMonitor.isDreaming()).thenReturn(false);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+
+ final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).post(dismissKeyguardRunnableCaptor.capture());
+
+ // Verify that the power manager was not told to wake up.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+ dismissKeyguardRunnableCaptor.getValue().run();
+ // Verify that the keyguard controller is told to unlock.
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ }
+
+
+ @Test
public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
// GIVEN side fingerprint enrolled, last wake reason was power button
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -537,6 +601,11 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
}
+ private void givenDreamingLocked() {
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ }
+
private void givenFingerprintModeUnlockCollapsing() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 3d35233ad646..530085191e96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -129,7 +129,6 @@ import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
@@ -252,7 +251,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private AssistManager mAssistManager;
@Mock private NotificationGutsManager mNotificationGutsManager;
@@ -276,6 +274,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ @Mock private Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
@@ -428,10 +428,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
+ when(mNotificationShadeWindowViewControllerLazy.get())
+ .thenReturn(mNotificationShadeWindowViewController);
when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
- when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
- mNotificationShadeWindowViewController);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -510,6 +510,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
+ mNotificationShadeWindowViewControllerLazy,
mNotificationShelfController,
mStackScrollerController,
mDozeParameters,
@@ -586,9 +587,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
mKeyguardVieMediatorCallback);
- // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically and make NPVC private.
- mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
+ // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
+ // below values initialized automatically.
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
@@ -823,8 +823,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
*/
@Test
public void testPredictiveBackCallback_invocationCollapsesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -841,8 +839,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
*/
@Test
public void testPredictiveBackAnimation_progressMaxScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -864,8 +860,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
*/
@Test
public void testPredictiveBackAnimation_progressMinScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 6a4b3c55f244..df3c1e50786b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,7 +22,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE
import static junit.framework.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -45,8 +44,6 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -65,20 +62,13 @@ public class LightBarControllerTest extends SysuiTestCase {
private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private LightBarTransitionsController mLightBarTransitionsController;
private LightBarTransitionsController mNavBarController;
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
- /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
- protected boolean testNewLightBarLogic() {
- return false;
- }
-
@Before
public void setup() {
- mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
mNavBarController = mock(LightBarTransitionsController.class);
when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
@@ -90,7 +80,6 @@ public class LightBarControllerTest extends SysuiTestCase {
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
- mFeatureFlags,
mock(DumpManager.class),
new FakeDisplayTracker(mContext));
}
@@ -211,8 +200,6 @@ public class LightBarControllerTest extends SysuiTestCase {
@Test
public void validateNavBarChangesUpdateIcons() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the launcher in dark mode buttons are light
mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -251,8 +238,6 @@ public class LightBarControllerTest extends SysuiTestCase {
@Test
public void navBarHasDarkIconsInLockedShade_lightMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -287,8 +272,6 @@ public class LightBarControllerTest extends SysuiTestCase {
@Test
public void navBarHasLightIconsInLockedShade_darkMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2d96e594592c..c8ec1bf4af9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -204,6 +204,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
userChipViewModel,
centralSurfacesImpl,
shadeControllerImpl,
+ shadeViewController,
shadeLogger,
viewUtil,
configurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index bbc75c9eeae8..0dc1d9a4b177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -59,12 +59,12 @@ import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -77,9 +77,11 @@ import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.utils.os.FakeHandler;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.google.common.truth.Expect;
@@ -100,6 +102,7 @@ import java.util.HashSet;
import java.util.Map;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -113,6 +116,9 @@ public class ScrimControllerTest extends SysuiTestCase {
private final LargeScreenShadeInterpolator
mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private ScrimController mScrimController;
private ScrimView mScrimBehind;
private ScrimView mNotificationsScrim;
@@ -136,13 +142,13 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@Mock private CoroutineDispatcher mMainDispatcher;
@Mock private TypedArray mMockTypedArray;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -274,20 +280,25 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager,
mConfigurationController,
new FakeExecutor(new FakeSystemClock()),
+ mJavaAdapter,
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
+ mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
mScrimController.setHasBackdrop(false);
- mScrimController.setWallpaperSupportsAmbientMode(false);
+
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
}
@@ -388,7 +399,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void transitionToAod_withAodWallpaper() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -410,7 +423,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
mScrimController.setHasBackdrop(true);
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -427,7 +442,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
mScrimController.setHasBackdrop(true);
@@ -540,7 +557,9 @@ public class ScrimControllerTest extends SysuiTestCase {
// Pre-condition
// Need to go to AoD first because PULSING doesn't change
// the back scrim opacity - otherwise it would hide AoD wallpapers.
- mScrimController.setWallpaperSupportsAmbientMode(false);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -968,19 +987,22 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager,
mConfigurationController,
new FakeExecutor(new FakeSystemClock()),
+ mJavaAdapter,
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
+ mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
mScrimController.setHasBackdrop(false);
- mScrimController.setWallpaperSupportsAmbientMode(false);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+ mTestScope.getTestScheduler().runCurrent();
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
@@ -1105,7 +1127,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testWillHideAodWallpaper() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1116,7 +1140,8 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testWillHideDockedWallpaper() {
mAlwaysOnEnabled = false;
when(mDockManager.isDocked()).thenReturn(true);
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
mScrimController.transitionTo(ScrimState.AOD);
@@ -1165,7 +1190,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testHidesShowWhenLockedActivity() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.setKeyguardOccluded(true);
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
@@ -1182,7 +1209,9 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
- mScrimController.setWallpaperSupportsAmbientMode(true);
+ mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+ mTestScope.getTestScheduler().runCurrent();
+
mScrimController.transitionTo(ScrimState.AOD);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5bd6ff4e73f2..cd8aaa2685c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -42,7 +42,6 @@ import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeNotificationPresenter;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -52,7 +51,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
@@ -86,14 +84,11 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(NotificationsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
- private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
private final InitController mInitController = new InitController();
@Before
public void setup() {
mMetricsLogger = new FakeMetricsLogger();
- LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
- mMetricsLogger);
mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
mDependency.injectTestDependency(StatusBarStateController.class,
mock(SysuiStatusBarStateController.class));
@@ -111,8 +106,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
ShadeViewController shadeViewController = mock(ShadeViewController.class);
- when(shadeViewController.getShadeNotificationPresenter())
- .thenReturn(mock(ShadeNotificationPresenter.class));
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
@@ -135,11 +128,9 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(NotifShadeEventSource.class),
mock(NotificationMediaManager.class),
mock(NotificationGutsManager.class),
- lockscreenGestureLogger,
mInitController,
mNotificationInterruptStateProvider,
mock(NotificationRemoteInputManager.class),
- mNotifPipelineFlags,
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
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 27957ed06d11..f299ad45a7ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.Application;
@@ -36,6 +37,7 @@ import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
import android.os.Parcel;
@@ -74,6 +76,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -400,6 +404,18 @@ public class ToastUITest extends SysuiTestCase {
verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
}
+ @Test
+ public void testShowToast_invalidDisplayId_logsAndSkipsToast() {
+ int invalidDisplayId = getInvalidDisplayId();
+
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback, invalidDisplayId);
+
+ verify(mToastLogger).logOnSkipToastForInvalidDisplay(PACKAGE_NAME_1, TOKEN_1.toString(),
+ invalidDisplayId);
+ verifyZeroInteractions(mWindowManager);
+ }
+
private View verifyWmAddViewAndAttachToParent() {
ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
verify(mWindowManager).addView(viewCaptor.capture(), any());
@@ -416,4 +432,10 @@ public class ToastUITest extends SysuiTestCase {
return null;
};
}
+
+ private int getInvalidDisplayId() {
+ return Arrays.stream(
+ mContext.getSystemService(DisplayManager.class).getDisplays())
+ .map(Display::getDisplayId).max(Integer::compare).get() + 1;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index d8e418a7815c..b13cb72dc944 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -26,6 +26,7 @@ import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -56,6 +57,7 @@ class WallpaperControllerTest : SysuiTestCase() {
private lateinit var viewRootImpl: ViewRootImpl
@Mock
private lateinit var windowToken: IBinder
+ private val wallpaperRepository = FakeWallpaperRepository()
@JvmField
@Rule
@@ -69,7 +71,7 @@ class WallpaperControllerTest : SysuiTestCase() {
`when`(root.windowToken).thenReturn(windowToken)
`when`(root.isAttachedToWindow).thenReturn(true)
- wallaperController = WallpaperController(wallpaperManager)
+ wallaperController = WallpaperController(wallpaperManager, wallpaperRepository)
wallaperController.rootView = root
}
@@ -90,9 +92,9 @@ class WallpaperControllerTest : SysuiTestCase() {
@Test
fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
- wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+ wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
useDefaultTransition = false
- ))
+ )
wallaperController.setUnfoldTransitionZoom(0.5f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 0c77529377ab..c81910855f78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -51,6 +52,7 @@ import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
@@ -117,6 +119,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
};
+ private FakeFeatureFlags mFeatureFlags;
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -132,6 +136,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mConfigurationController = new FakeConfigurationController();
+ mFeatureFlags = new FakeFeatureFlags();
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -146,7 +152,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -254,6 +261,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testVibrateOnRingerChangedToVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialSilentState = new State();
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
@@ -274,7 +282,30 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
@Test
+ public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialSilentState = new State();
+ initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // change ringer to silent
+ mDialog.onStateChangedH(initialSilentState);
+
+ // expected: shouldn't call vibrate yet
+ verify(mVolumeDialogController, never()).vibrate(any());
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // expected: vibrate method of controller is not used
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testNoVibrateOnRingerInitialization() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = -1;
@@ -292,7 +323,42 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
@Test
+ public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = -1;
+
+ // ringer not initialized yet:
+ mDialog.onStateChangedH(initialUnsetState);
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // shouldn't call vibrate on the controller either
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testSelectVibrateFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerVibrate.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_VIBRATE, false);
+ }
+
+ @Test
+ public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -307,6 +373,22 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectMuteFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerMute.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_SILENT, false);
+ }
+
+ @Test
+ public void testSelectMuteFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -321,6 +403,22 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectNormalFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerNormal.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_NORMAL, false);
+ }
+
+ @Test
+ public void testSelectNormalFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
mDialog.onStateChangedH(initialUnsetState);
@@ -383,7 +481,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0 , null);
@@ -423,7 +522,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -462,7 +562,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -503,7 +604,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags
+ );
dialog.init(0, null);
verify(mPostureController, never()).removeCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
new file mode 100644
index 000000000000..fe5024fdc0a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -0,0 +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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of the wallpaper repository. */
+class FakeWallpaperRepository : WallpaperRepository {
+ override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
+ override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
new file mode 100644
index 000000000000..f8b096a7579b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperRepositoryImplTest : SysuiTestCase() {
+
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val userRepository = FakeUserRepository()
+ private val wallpaperManager: WallpaperManager = mock()
+
+ private val underTest: WallpaperRepositoryImpl by lazy {
+ WallpaperRepositoryImpl(
+ testScope.backgroundScope,
+ fakeBroadcastDispatcher,
+ userRepository,
+ wallpaperManager,
+ context,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ true,
+ )
+ }
+
+ @Test
+ fun wallpaperInfo_nullInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_hasInfoFromManager() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isEqualTo(UNSUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_initialValueIsFetched() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnUserChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user is switched to user4
+ userRepository.setSelectedUserInfo(user4)
+
+ // THEN user4's wallpaper is used
+ assertThat(latest).isEqualTo(user4Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_doesNotUpdateOnUserChanging() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user has started switching to user4 but hasn't finished yet
+ userRepository.selectedUser.value =
+ SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // THEN the wallpaper still matches user3
+ assertThat(latest).isEqualTo(user3Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+
+ assertThat(latest).isEqualTo(wp1)
+
+ // WHEN the info is new and a broadcast is sent
+ val wp2 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(wp2)
+ }
+
+ @Test
+ fun wallpaperInfo_wallpaperNotSupported_alwaysNull() =
+ testScope.runTest {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ false
+ )
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_nullInfo_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_infoSupports_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_updatesOnUserChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+ // WHEN a user with supported wallpaper is selected
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // THEN it's true
+ assertThat(latest).isTrue()
+
+ // WHEN the user is switched to a user with unsupported wallpaper
+ userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+ // THEN it's false
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+ .thenReturn(UNSUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+ // WHEN a user with supported wallpaper is selected
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // THEN it's true
+ assertThat(latest).isTrue()
+
+ // WHEN the user has started switching to a user with unsupported wallpaper but hasn't
+ // finished yet
+ userRepository.selectedUser.value =
+ SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // THEN it still matches the old user
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_updatesOnIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ assertThat(latest).isFalse()
+
+ // WHEN the info now supports ambient mode and a broadcast is sent
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the flow updates
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() =
+ testScope.runTest {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+ assertThat(latest).isFalse()
+
+ // Even WHEN the current wallpaper *does* support ambient mode
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still false because wallpaper isn't supported
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ false
+ )
+
+ val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+ assertThat(latest).isFalse()
+
+ // Even WHEN the current wallpaper *does* support ambient mode
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still false because the device doesn't support it
+ assertThat(latest).isFalse()
+ }
+
+ private companion object {
+ val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val UNSUPPORTED_WP =
+ mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(false) }
+
+ val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val SUPPORTED_WP =
+ mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+ }
+}
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 bebdd4004c3d..c2e1ac70af80 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
@@ -31,16 +31,13 @@ class FakeAuthenticationRepository(
private val currentTime: () -> Long,
) : AuthenticationRepository {
- private val _isBypassEnabled = MutableStateFlow(false)
- override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
-
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 = 6
+ override val hintedPinLength: Int = HINTING_PIN_LENGTH
private val _isPatternVisible = MutableStateFlow(true)
override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
@@ -82,11 +79,7 @@ class FakeAuthenticationRepository(
}
override suspend fun getPinLength(): Int {
- return (credentialOverride ?: listOf(1, 2, 3, 4)).size
- }
-
- override fun setBypassEnabled(isBypassEnabled: Boolean) {
- _isBypassEnabled.value = isBypassEnabled
+ return (credentialOverride ?: DEFAULT_PIN).size
}
override suspend fun getFailedAuthenticationAttemptCount(): Int {
@@ -148,6 +141,15 @@ class FakeAuthenticationRepository(
}
}
+ private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
+ return when (val credentialType = getCurrentCredentialType(securityMode)) {
+ LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
+ else -> error("Unsupported credential type $credentialType!")
+ }
+ }
+
companion object {
val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
val PATTERN =
@@ -162,6 +164,8 @@ class FakeAuthenticationRepository(
)
const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
const val THROTTLE_DURATION_MS = 30000
+ const val HINTING_PIN_LENGTH = 6
+ val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
return when (this) {
@@ -188,15 +192,6 @@ class FakeAuthenticationRepository(
}
}
- private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
- return when (val credentialType = getCurrentCredentialType(securityMode)) {
- LockPatternUtils.CREDENTIAL_TYPE_PIN -> listOf(1, 2, 3, 4)
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
- else -> error("Unsupported credential type $credentialType!")
- }
- }
-
private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
@Suppress("UNCHECKED_CAST")
return when {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a5241244..0c5e43809fab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@ import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
- private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isInitialized = _isInitialized.asStateFlow()
-
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId = _sensorId.asStateFlow()
private val _strength: MutableStateFlow<SensorStrength> =
MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
private val _sensorType: MutableStateFlow<FingerprintSensorType> =
MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+ override val sensorType = _sensorType.asStateFlow()
private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
+ override val sensorLocations = _sensorLocations.asStateFlow()
fun setProperties(
sensorId: Int,
@@ -54,6 +49,5 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
- _isInitialized.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 2715aaa82253..548169e6cccd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,16 +29,16 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
- private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
- override val authenticationStatus: Flow<AuthenticationStatus> =
+ private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> =
_authenticationStatus.filterNotNull()
- fun setAuthenticationStatus(status: AuthenticationStatus) {
+ fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
_authenticationStatus.value = status
}
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
- fun setDetectionStatus(status: DetectionStatus) {
+ fun setDetectionStatus(status: FaceDetectionStatus) {
_detectionStatus.value = status
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 4bfd3d64c98e..38791caf5bfc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,32 +17,38 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
-
- private val _isRunning = MutableStateFlow(false)
- override val isRunning: Flow<Boolean>
- get() = _isRunning
-
- private var fpSensorType = MutableStateFlow<BiometricType?>(null)
- override val availableFpSensorType: Flow<BiometricType?>
- get() = fpSensorType
-
fun setLockedOut(lockedOut: Boolean) {
_isLockedOut.value = lockedOut
}
+ private val _isRunning = MutableStateFlow(false)
+ override val isRunning: Flow<Boolean>
+ get() = _isRunning
fun setIsRunning(value: Boolean) {
_isRunning.value = value
}
+ private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+ override val availableFpSensorType: Flow<BiometricType?>
+ get() = fpSensorType
fun setAvailableFpSensorType(value: BiometricType?) {
fpSensorType.value = value
}
+
+ private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
}
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 63097401bc5a..8428566270de 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
@@ -22,11 +22,14 @@ import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -56,6 +59,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _isDozing = MutableStateFlow(false)
override val isDozing: StateFlow<Boolean> = _isDozing
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -86,6 +92,9 @@ class FakeKeyguardRepository : KeyguardRepository {
)
override val wakefulness = _wakefulnessModel
+ private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF))
+ override val screenModel = _screenModel
+
private val _isUdfpsSupported = MutableStateFlow(false)
private val _isKeyguardGoingAway = MutableStateFlow(false)
@@ -114,6 +123,11 @@ class FakeKeyguardRepository : KeyguardRepository {
return _isKeyguardShowing.value
}
+ private var _isBypassEnabled = false
+ override fun isBypassEnabled(): Boolean {
+ return _isBypassEnabled
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -142,6 +156,10 @@ class FakeKeyguardRepository : KeyguardRepository {
_isDozing.value = isDozing
}
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
override fun setLastDozeTapToWakePosition(position: Point) {
_lastDozeTapToWakePosition.value = position
}
@@ -198,6 +216,10 @@ class FakeKeyguardRepository : KeyguardRepository {
_isKeyguardUnlocked.value = isUnlocked
}
+ fun setBypassEnabled(isEnabled: Boolean) {
+ _isBypassEnabled = isEnabled
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
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 47e1daf4008c..931798130499 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
@@ -142,6 +142,7 @@ class SceneTestUtils(
repository = repository,
backgroundDispatcher = testDispatcher,
userRepository = userRepository,
+ keyguardRepository = keyguardRepository,
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
diff --git a/services/Android.bp b/services/Android.bp
index b0a0e5e44a8c..453f57234145 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -159,6 +159,7 @@ java_library {
"services.coverage",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.midi",
"services.musicsearch",
"services.net",
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index cd2f844294e3..254e6ce14630 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1094,8 +1094,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
void onEnteringPipBlocked(int uid) {
- showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
- Toast.LENGTH_LONG, mContext.getMainLooper());
+ // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
+ // support PiP.
}
void playSoundEffect(int effectType) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c1239d53058c..7d46c0cd075f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -256,7 +256,7 @@ import java.util.function.Predicate;
public final class ActiveServices {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+ static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
@@ -850,8 +850,7 @@ public final class ActiveServices {
// Service.startForeground()), at that point we will consult the BFSL check and the timeout
// and make the necessary decisions.
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
- backgroundStartPrivileges, false /* isBindService */,
- !fgRequired /* isStartService */);
+ backgroundStartPrivileges, false /* isBindService */);
if (!mAm.mUserController.exists(r.userId)) {
Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,7 +893,7 @@ public final class ActiveServices {
if (fgRequired) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+ if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
String msg = "startForegroundService() not allowed due to "
+ "mAllowStartForeground false: service "
+ r.shortInstanceName;
@@ -1060,7 +1059,7 @@ public final class ActiveServices {
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.mAllowWhileInUsePermissionInFgsReason);
+ r.getFgsAllowWIU());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2178,12 +2177,12 @@ public final class ActiveServices {
// on a SHORT_SERVICE FGS.
// See if the app could start an FGS or not.
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowStart();
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
- if (r.mAllowStartForeground == REASON_DENIED) {
+ false /* isBindService */);
+ if (!r.isFgsAllowedStart()) {
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL DENIED.");
} else {
@@ -2191,13 +2190,13 @@ public final class ActiveServices {
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL Allowed: "
+ PowerExemptionManager.reasonCodeToString(
- r.mAllowStartForeground));
+ r.getFgsAllowStart()));
}
}
final boolean fgsStartAllowed =
!isBgFgsRestrictionEnabledForService
- || (r.mAllowStartForeground != REASON_DENIED);
+ || r.isFgsAllowedStart();
if (fgsStartAllowed) {
if (isNewTypeShortFgs) {
@@ -2246,7 +2245,7 @@ public final class ActiveServices {
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -2266,20 +2265,21 @@ public final class ActiveServices {
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.mAllowWhileInUsePermissionInFgs) {
+ if (!r.isFgsAllowedWIU()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
+ r.shortInstanceName);
}
+ r.maybeLogFgsLogicChange();
if (!bypassBfslCheck) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED
+ if (!r.isFgsAllowedStart()
&& isBgFgsRestrictionEnabledForService) {
final String msg = "Service.startForeground() not allowed due to "
+ "mAllowStartForeground false: service "
@@ -2378,9 +2378,9 @@ public final class ActiveServices {
// The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
// be deferred, make a copy of mAllowStartForeground and
// mAllowWhileInUsePermissionInFgs.
- r.mAllowStartForegroundAtEntering = r.mAllowStartForeground;
+ r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.mAllowWhileInUsePermissionInFgs;
+ r.isFgsAllowedWIU();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2558,7 +2558,7 @@ public final class ActiveServices {
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.mAllowWhileInUsePermissionInFgs, policyInfo);
+ r.isFgsAllowedWIU(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -3701,9 +3701,7 @@ public final class ActiveServices {
}
clientPsr.addConnection(c);
c.startAssociationIfNeeded();
- // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
- // dropping the process' adjustment level.
- if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+ if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
clientPsr.setHasAboveClient(true);
}
if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
@@ -3744,8 +3742,7 @@ public final class ActiveServices {
}
}
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
- BackgroundStartPrivileges.NONE, true /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, true /* isBindService */);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -7443,54 +7440,80 @@ public final class ActiveServices {
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isStartService True if it's called from Context.startService().
- * False if it's called from Context.startForegroundService() or
- * Service.startForeground().
+ * @param isBindService True if it's called from bindService().
* @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
- boolean isStartService) {
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+
+ @ReasonCode int allowWIU;
+ @ReasonCode int allowStart;
+
+ // If called from bindService(), do not update the actual fields, but instead
+ // keep it in a separate set of fields.
+ if (isBindService) {
+ allowWIU = r.mAllowWIUInBindService;
+ allowStart = r.mAllowStartInBindService;
+ } else {
+ allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
+ allowStart = r.mAllowStartForegroundNoBinding;
+ }
+
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (!r.mAllowWhileInUsePermissionInFgs) {
+ if (allowWIU == REASON_DENIED) {
// BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
// Note REASON_OTHER since there's no other suitable reason.
- r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
+ allowWIU = REASON_OTHER;
}
- r.mAllowWhileInUsePermissionInFgs = true;
}
- if (!r.mAllowWhileInUsePermissionInFgs
- || (r.mAllowStartForeground == REASON_DENIED)) {
+ if ((allowWIU == REASON_DENIED)
+ || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (!r.mAllowWhileInUsePermissionInFgs) {
- r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
- r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
+ if (allowWIU == REASON_DENIED) {
+ allowWIU = allowWhileInUse;
}
- if (r.mAllowStartForeground == REASON_DENIED) {
- r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
+ if (allowStart == REASON_DENIED) {
+ allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
}
+
+ if (isBindService) {
+ r.mAllowWIUInBindService = allowWIU;
+ r.mAllowStartInBindService = allowStart;
+ } else {
+ r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
+ r.mAllowStartForegroundNoBinding = allowStart;
+
+ // Also do a binding client check, unless called from bindService().
+ if (r.mAllowWIUByBindings == REASON_DENIED) {
+ r.mAllowWIUByBindings =
+ shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
+ }
+ if (r.mAllowStartByBindings == REASON_DENIED) {
+ r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ }
+ }
}
/**
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.mAllowWhileInUsePermissionInFgs = false;
- r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowWIU();
+ r.clearFgsAllowStart();
+
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8062,10 +8085,10 @@ public final class ActiveServices {
*/
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
- + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
+ + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ")
+ r.mInfoAllowStartForeground
+ (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
- if (r.mAllowStartForeground != REASON_DENIED) {
+ if (r.isFgsAllowedStart()) {
if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
Slog.wtfQuiet(TAG, msg);
@@ -8105,8 +8128,8 @@ public final class ActiveServices {
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs;
- fgsStartReasonCode = r.mAllowStartForeground;
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
@@ -8295,8 +8318,7 @@ public final class ActiveServices {
r.mFgsEnterTime = SystemClock.uptimeMillis();
r.foregroundServiceType = options.mForegroundServiceTypes;
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
- BackgroundStartPrivileges.NONE, false /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
final boolean newService = psr.startService(r);
// updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7ba720eedfba..81858ee6e2c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -559,6 +559,10 @@ public class ActivityManagerService extends IActivityManager.Stub
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // How long we wait for a launched process to complete its app startup before we ANR.
+ static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
// How long we wait to kill an application zygote, after the last process using
// it has gone away.
static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
@@ -1624,6 +1628,7 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
static final int ADD_UID_TO_OBSERVER_MSG = 80;
static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
+ static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1976,6 +1981,16 @@ public class ActivityManagerService extends IActivityManager.Stub
case UPDATE_CACHED_APP_HIGH_WATERMARK: {
mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
} break;
+ case BIND_APPLICATION_TIMEOUT_MSG: {
+ ProcessRecord app = (ProcessRecord) msg.obj;
+
+ final String anrMessage;
+ synchronized (app) {
+ anrMessage = "Process " + app + " failed to complete startup";
+ }
+
+ mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+ } break;
}
}
}
@@ -4734,6 +4749,12 @@ public class ActivityManagerService extends IActivityManager.Stub
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
}
+
+ Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+ msg.obj = app;
+ mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
if (profilerInfo != null) {
profilerInfo.closeFd();
profilerInfo = null;
@@ -4808,7 +4829,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
} else {
Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ ". Uid: " + uid);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f420619db490..5d0fefc75a28 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -249,6 +249,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private static final int MSG_CHECK_HEALTH = 5;
private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
+ private static final int MSG_UID_STATE_CHANGED = 8;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -295,6 +296,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
return true;
}
+ case MSG_UID_STATE_CHANGED: {
+ final int uid = (int) msg.obj;
+ final int procState = msg.arg1;
+ synchronized (mService) {
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ mUidForeground.put(uid, true);
+ } else {
+ mUidForeground.delete(uid);
+ }
+ refreshProcessQueuesLocked(uid);
+ }
+ return true;
+ }
}
return false;
};
@@ -672,7 +686,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
@Override
public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) {
mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app);
- mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app));
+ mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget();
}
@Override
@@ -1601,14 +1615,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
- synchronized (mService) {
- if (procState == ActivityManager.PROCESS_STATE_TOP) {
- mUidForeground.put(uid, true);
- } else {
- mUidForeground.delete(uid);
- }
- refreshProcessQueuesLocked(uid);
- }
+ mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid);
+ mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid)
+ .sendToTarget();
}
}, ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, "android");
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 38e7371e7075..4f5b5e1fbd68 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -479,8 +479,8 @@ public class ForegroundServiceTypeLoggerModule {
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs
- r.mAllowStartForeground, // fgsStartReasonCode
+ r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
0, // callerTargetSdkVersion
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a682c85f03b2..459c6ff3504a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2222,7 +2222,7 @@ public class OomAdjuster {
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.mAllowWhileInUsePermissionInFgs) {
+ if (s.isFgsAllowedWIU()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 7ff6d116baaf..81d0b6ac700b 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -341,8 +341,7 @@ final class ProcessServiceRecord {
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.binding.service.app.mServices != this
- && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+ if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 50fe6d71d26e..aabab61c36f4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,9 +21,11 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.reasonCodeToString;
import static android.os.Process.INVALID_UID;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ActiveServices.TAG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -172,11 +174,11 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
BackgroundStartPrivileges.NONE;
- // allow while-in-use permissions in foreground service or not.
+ // Reason code for allow while-in-use permissions in foreground service.
+ // If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
- boolean mAllowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
+ int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -205,15 +207,114 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
- @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundNoBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
- @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundAtEntering = REASON_DENIED;
// Debug info why mAllowStartForeground is allowed or denied.
String mInfoAllowStartForeground;
// Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
// Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
boolean mLoggedInfoAllowStartForeground;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWIU() {
+ return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
+ ? mAllowWhileInUsePermissionInFgsReasonNoBinding
+ : mAllowWIUInBindService;
+ }
+
+ boolean isFgsAllowedWIU() {
+ return getFgsAllowWIU() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowStart() {
+ return mAllowStartForegroundNoBinding != REASON_DENIED
+ ? mAllowStartForegroundNoBinding
+ : mAllowStartInBindService;
+ }
+
+ boolean isFgsAllowedStart() {
+ return getFgsAllowStart() != REASON_DENIED;
+ }
+
+ void clearFgsAllowWIU() {
+ mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ mAllowWIUInBindService = REASON_DENIED;
+ mAllowWIUByBindings = REASON_DENIED;
+ }
+
+ void clearFgsAllowStart() {
+ mAllowStartForegroundNoBinding = REASON_DENIED;
+ mAllowStartInBindService = REASON_DENIED;
+ mAllowStartByBindings = REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return first != REASON_DENIED ? first : second;
+ }
+
+ boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return (first == REASON_DENIED) != (second == REASON_DENIED);
+ }
+
+ String changeMessage(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return reasonOr(first, second) == REASON_DENIED ? "DENIED"
+ : ("ALLOWED ("
+ + reasonCodeToString(first)
+ + "+"
+ + reasonCodeToString(second)
+ + ")");
+ }
+
+ void maybeLogFgsLogicChange() {
+ final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService);
+ final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUByBindings);
+ final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
+ final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+
+ final boolean wiuChanged = allowedChanged(origWiu, newWiu);
+ final boolean startChanged = allowedChanged(origStart, newStart);
+
+ if (!wiuChanged && !startChanged) {
+ return;
+ }
+ final String message = "FGS logic changed:"
+ + (wiuChanged ? " [WIU changed]" : "")
+ + (startChanged ? " [BFSL changed]" : "")
+ + " OW:" // Orig-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService)
+ + " NW:" // New-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
+ + " OS:" // Orig-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+ + " NS:" // New-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ Slog.wtf(TAG_SERVICE, message);
+ }
+
// The number of times Service.startForeground() is called, after this service record is
// created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
// is called.
@@ -502,7 +603,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- mAllowWhileInUsePermissionInFgs);
+ isFgsAllowedWIU());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -618,7 +719,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason));
+ pw.println(PowerExemptionManager.reasonCodeToString(
+ mAllowWhileInUsePermissionInFgsReasonNoBinding));
+
+ pw.print(prefix); pw.print("mAllowWIUInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
+ pw.print(prefix); pw.print("mAllowWIUByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
@@ -626,7 +733,12 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground));
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
+ pw.print(prefix); pw.print("mAllowStartInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
+ pw.print(prefix); pw.print("mAllowStartByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("infoAllowStartForeground=");
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
index 6727fbcdec66..058ea6bbb696 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -17,9 +17,16 @@
package com.android.server.biometrics;
/**
- * Interface for biometric operations to get camera privacy state.
+ * Interface for biometrics to get camera status.
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
+public interface BiometricCameraManager {
+ /**
+ * Returns true if any camera is in use.
+ */
+ boolean isAnyCameraUnavailable();
+
+ /**
+ * Returns true if privacy is enabled and camera access is disabled.
+ */
boolean isCameraPrivacyEnabled();
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 000000000000..000ee5446962
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+ private final CameraManager mCameraManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+ private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+ private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraAvailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, true);
+ }
+
+ @Override
+ public void onCameraUnavailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, false);
+ }
+ };
+
+ public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+ @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ mCameraManager = cameraManager;
+ mSensorPrivacyManager = sensorPrivacyManager;
+ mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+ }
+
+ @Override
+ public boolean isAnyCameraUnavailable() {
+ for (String cameraId : mIsCameraAvailable.keySet()) {
+ if (!mIsCameraAvailable.get(cameraId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
deleted file mode 100644
index b6701da1d348..000000000000
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics;
-
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-
-import android.annotation.Nullable;
-import android.hardware.SensorPrivacyManager;
-
-public class BiometricSensorPrivacyImpl implements
- BiometricSensorPrivacy {
- private final SensorPrivacyManager mSensorPrivacyManager;
-
- public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
- mSensorPrivacyManager = sensorPrivacyManager;
- }
-
- @Override
- public boolean isCameraPrivacyEnabled() {
- return mSensorPrivacyManager != null && mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1fa97a3cb97f..279aaf9d3253 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -48,6 +50,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri;
@@ -125,7 +128,7 @@ public class BiometricService extends SystemService {
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
/**
* Tracks authenticatorId invalidation. For more details, see
@@ -368,7 +371,7 @@ public class BiometricService extends SystemService {
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
int userId) {
switch (modality) {
- case BiometricAuthenticator.TYPE_FACE:
+ case TYPE_FACE:
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -566,22 +569,6 @@ public class BiometricService extends SystemService {
Utils.combineAuthenticatorBundles(promptInfo);
- // Set the default title if necessary.
- if (promptInfo.isUseDefaultTitle()) {
- if (TextUtils.isEmpty(promptInfo.getTitle())) {
- promptInfo.setTitle(getContext()
- .getString(R.string.biometric_dialog_default_title));
- }
- }
-
- // Set the default subtitle if necessary.
- if (promptInfo.isUseDefaultSubtitle()) {
- if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
- promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_default_subtitle));
- }
- }
-
final long requestId = mRequestCounter.get();
mHandler.post(() -> handleAuthenticate(
token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -936,7 +923,7 @@ public class BiometricService extends SystemService {
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
}
/**
@@ -1030,9 +1017,9 @@ public class BiometricService extends SystemService {
return context.getSystemService(UserManager.class);
}
- public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
- return new BiometricSensorPrivacyImpl(context.getSystemService(
- SensorPrivacyManager.class));
+ public BiometricCameraManager getBiometricCameraManager(Context context) {
+ return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+ context.getSystemService(SensorPrivacyManager.class));
}
}
@@ -1062,7 +1049,7 @@ public class BiometricService extends SystemService {
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
- mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
+ mBiometricCameraManager = injector.getBiometricCameraManager(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1299,7 +1286,34 @@ public class BiometricService extends SystemService {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
+
+ // Set the default title if necessary.
+ if (promptInfo.isUseDefaultTitle()) {
+ if (TextUtils.isEmpty(promptInfo.getTitle())) {
+ promptInfo.setTitle(getContext()
+ .getString(R.string.biometric_dialog_default_title));
+ }
+ }
+
+ final int eligible = preAuthInfo.getEligibleModalities();
+ final boolean hasEligibleFingerprintSensor =
+ (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
+ final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;
+
+ // Set the subtitle according to the modality.
+ if (promptInfo.isUseDefaultSubtitle()) {
+ if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_default_subtitle));
+ } else if (hasEligibleFingerprintSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_fingerprint_subtitle));
+ } else if (hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_face_subtitle));
+ }
+ }
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index e6f25cb88006..b1740a780539 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -72,16 +72,16 @@ class PreAuthInfo {
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
+ Context context, BiometricCameraManager biometricCameraManager) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
- mBiometricSensorPrivacy = biometricSensorPrivacy;
+ mBiometricCameraManager = biometricCameraManager;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -99,7 +99,7 @@ class PreAuthInfo {
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context,
- BiometricSensorPrivacy biometricSensorPrivacy)
+ BiometricCameraManager biometricCameraManager)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -127,7 +127,7 @@ class PreAuthInfo {
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- biometricSensorPrivacy);
+ biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -151,7 +151,7 @@ class PreAuthInfo {
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
}
/**
@@ -168,12 +168,16 @@ class PreAuthInfo {
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
+ boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -195,8 +199,8 @@ class PreAuthInfo {
return BIOMETRIC_NOT_ENROLLED;
}
- if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
- if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+ if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+ if (biometricCameraManager.isCameraPrivacyEnabled()) {
//Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
@@ -307,8 +311,8 @@ class PreAuthInfo {
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
boolean cameraPrivacyEnabled = false;
- if (mBiometricSensorPrivacy != null) {
- cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
+ if (mBiometricCameraManager != null) {
+ cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 0b04159194d1..f8f0088ac047 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -613,16 +613,26 @@ public class CameraServiceProxy extends SystemService
@Override
public boolean isCameraDisabled(int userId) {
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
- Slog.e(TAG, "Failed to get the device policy manager service");
+ if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
+ Slog.e(TAG, "Calling UID: " + Binder.getCallingUid()
+ + " doesn't match expected camera service UID!");
return false;
}
+ final long ident = Binder.clearCallingIdentity();
try {
- return dpm.getCameraDisabled(null, userId);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ Slog.e(TAG, "Failed to get the device policy manager service");
+ return false;
+ }
+ try {
+ return dpm.getCameraDisabled(null, userId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
};
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index c421ec04d6f5..59844e1afd1c 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -38,17 +38,24 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* This class monitors various conditions, such as skin temperature throttling status, and limits
* the allowed brightness range accordingly.
+ *
+ * @deprecated will be replaced by
+ * {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
*/
+@Deprecated
class BrightnessThrottler {
private static final String TAG = "BrightnessThrottler";
private static final boolean DEBUG = false;
@@ -93,8 +100,21 @@ class BrightnessThrottler {
// time the underlying display device changes.
// This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
// HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
- private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
- mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
+ private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
+ mThermalBrightnessThrottlingDataOverride = new HashMap<>();
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+ return new ThrottlingLevel(status, brightnessPoint);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+ mDataSetMapper = ThermalBrightnessThrottlingData::create;
BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
String throttlingDataId,
@@ -257,79 +277,15 @@ class BrightnessThrottler {
// 456,2,moderate,0.9,critical,0.7,id_2
// displayId, number, <state, val> * number
// displayId, <number, <state, val> * number>, throttlingId
- private boolean parseAndAddData(@NonNull String strArray,
- @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
- displayIdToThrottlingIdToBtd) {
- boolean validConfig = true;
- String[] items = strArray.split(",");
- int i = 0;
-
- try {
- String uniqueDisplayId = items[i++];
-
- // number of throttling points
- int noOfThrottlingPoints = Integer.parseInt(items[i++]);
- List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
-
- // throttling level and point
- for (int j = 0; j < noOfThrottlingPoints; j++) {
- String severity = items[i++];
- int status = parseThermalStatus(severity);
- float brightnessPoint = parseBrightness(items[i++]);
- throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
- }
-
- String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
- ThermalBrightnessThrottlingData throttlingLevelsData =
- DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
-
- // Add throttlingLevelsData to inner map where necessary.
- HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
- displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
- if (throttlingMapForDisplay == null) {
- throttlingMapForDisplay = new HashMap<>();
- throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
- displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
- } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
- Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
- + "contains duplicate throttling ids: '" + throttlingDataId + "'");
- return false;
- } else {
- throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
- }
- } catch (NumberFormatException | IndexOutOfBoundsException
- | UnknownThermalStatusException e) {
- Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
- validConfig = false;
- }
-
- if (i != items.length) {
- validConfig = false;
- }
- return validConfig;
- }
-
private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
- HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
- new HashMap<>(1);
mThermalBrightnessThrottlingDataString =
mConfigParameterProvider.getBrightnessThrottlingData();
- boolean validConfig = true;
mThermalBrightnessThrottlingDataOverride.clear();
if (mThermalBrightnessThrottlingDataString != null) {
- String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
- for (String s : throttlingDataSplits) {
- if (!parseAndAddData(s, tempThrottlingData)) {
- validConfig = false;
- break;
- }
- }
-
- if (validConfig) {
- mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
- tempThrottlingData.clear();
- }
-
+ Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(
+ mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
+ mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
} else {
Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
}
@@ -395,42 +351,6 @@ class BrightnessThrottler {
}
}
- private float parseBrightness(String intVal) throws NumberFormatException {
- float value = Float.parseFloat(intVal);
- if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
- throw new NumberFormatException("Brightness constraint value out of bounds.");
- }
- return value;
- }
-
- @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
- throws UnknownThermalStatusException {
- switch (value) {
- case "none":
- return PowerManager.THERMAL_STATUS_NONE;
- case "light":
- return PowerManager.THERMAL_STATUS_LIGHT;
- case "moderate":
- return PowerManager.THERMAL_STATUS_MODERATE;
- case "severe":
- return PowerManager.THERMAL_STATUS_SEVERE;
- case "critical":
- return PowerManager.THERMAL_STATUS_CRITICAL;
- case "emergency":
- return PowerManager.THERMAL_STATUS_EMERGENCY;
- case "shutdown":
- return PowerManager.THERMAL_STATUS_SHUTDOWN;
- default:
- throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
- }
- }
-
- private static class UnknownThermalStatusException extends Exception {
- UnknownThermalStatusException(String message) {
- super(message);
- }
- }
-
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index e27182fe3ea7..dd5afa2bdc39 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.text.TextUtils;
+
import com.android.server.display.brightness.BrightnessReason;
import java.util.Objects;
@@ -29,12 +31,14 @@ public final class DisplayBrightnessState {
private final float mSdrBrightness;
private final BrightnessReason mBrightnessReason;
private final String mDisplayBrightnessStrategyName;
+ private final boolean mShouldUseAutoBrightness;
private DisplayBrightnessState(Builder builder) {
- this.mBrightness = builder.getBrightness();
- this.mSdrBrightness = builder.getSdrBrightness();
- this.mBrightnessReason = builder.getBrightnessReason();
- this.mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mBrightness = builder.getBrightness();
+ mSdrBrightness = builder.getSdrBrightness();
+ mBrightnessReason = builder.getBrightnessReason();
+ mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
}
/**
@@ -66,6 +70,13 @@ public final class DisplayBrightnessState {
return mDisplayBrightnessStrategyName;
}
+ /**
+ * @return {@code true} if the device is set up to run auto-brightness.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -75,6 +86,8 @@ public final class DisplayBrightnessState {
stringBuilder.append(getSdrBrightness());
stringBuilder.append("\n brightnessReason:");
stringBuilder.append(getBrightnessReason());
+ stringBuilder.append("\n shouldUseAutoBrightness:");
+ stringBuilder.append(getShouldUseAutoBrightness());
return stringBuilder.toString();
}
@@ -91,28 +104,20 @@ public final class DisplayBrightnessState {
return false;
}
- DisplayBrightnessState
- displayBrightnessState = (DisplayBrightnessState) other;
+ DisplayBrightnessState otherState = (DisplayBrightnessState) other;
- if (mBrightness != displayBrightnessState.getBrightness()) {
- return false;
- }
- if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
- return false;
- }
- if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
- return false;
- }
- if (!mDisplayBrightnessStrategyName.equals(
- displayBrightnessState.getDisplayBrightnessStrategyName())) {
- return false;
- }
- return true;
+ return mBrightness == otherState.getBrightness()
+ && mSdrBrightness == otherState.getSdrBrightness()
+ && mBrightnessReason.equals(otherState.getBrightnessReason())
+ && TextUtils.equals(mDisplayBrightnessStrategyName,
+ otherState.getDisplayBrightnessStrategyName())
+ && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
}
@Override
public int hashCode() {
- return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+ return Objects.hash(
+ mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
}
/**
@@ -123,6 +128,23 @@ public final class DisplayBrightnessState {
private float mSdrBrightness;
private BrightnessReason mBrightnessReason = new BrightnessReason();
private String mDisplayBrightnessStrategyName;
+ private boolean mShouldUseAutoBrightness;
+
+ /**
+ * Create a builder starting with the values from the specified {@link
+ * DisplayBrightnessState}.
+ *
+ * @param state The state from which to initialize.
+ */
+ public static Builder from(DisplayBrightnessState state) {
+ Builder builder = new Builder();
+ builder.setBrightness(state.getBrightness());
+ builder.setSdrBrightness(state.getSdrBrightness());
+ builder.setBrightnessReason(state.getBrightnessReason());
+ builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
+ builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+ return builder;
+ }
/**
* Gets the brightness
@@ -200,6 +222,21 @@ public final class DisplayBrightnessState {
}
/**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public Builder setShouldUseAutoBrightness(boolean shouldUseAutoBrightness) {
+ this.mShouldUseAutoBrightness = shouldUseAutoBrightness;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
+ /**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d9cb299db69f..c25b25312c89 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -458,7 +458,7 @@ public class DisplayDeviceConfig {
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
- static final String DEFAULT_ID = "default";
+ public static final String DEFAULT_ID = "default";
private static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -3127,11 +3127,15 @@ public class DisplayDeviceConfig {
public static class ThermalBrightnessThrottlingData {
public List<ThrottlingLevel> throttlingLevels;
- static class ThrottlingLevel {
+ /**
+ * thermal status to brightness cap holder
+ */
+ public static class ThrottlingLevel {
public @PowerManager.ThermalStatus int thermalStatus;
public float brightness;
- ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+ public ThrottlingLevel(
+ @PowerManager.ThermalStatus int thermalStatus, float brightness) {
this.thermalStatus = thermalStatus;
this.brightness = brightness;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index dbe15b6e2da8..3b779ecf77e5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -155,6 +155,7 @@ import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
@@ -540,7 +541,8 @@ public final class DisplayManagerService extends SystemService {
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler);
+ new LogicalDisplayListener(), mSyncRoot, mHandler,
+ new FoldSettingWrapper(mContext.getContentResolver()));
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ffecf2b7018d..c5d0c177a46d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -141,6 +141,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
private static final int MSG_SWITCH_USER = 14;
private static final int MSG_BOOT_COMPLETED = 15;
+ private static final int MSG_SET_DWBC_STRONG_MODE = 16;
+ private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
+ private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -436,6 +439,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final boolean mSkipScreenOnBrightnessRamp;
// Display white balance components.
+ // Critical methods must be called on DPC handler thread.
@Nullable
private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
@Nullable
@@ -680,9 +684,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
try {
+ displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+ mHandler, mSensorManager, resources);
displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
- displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
- mSensorManager, resources);
displayWhiteBalanceSettings.setCallbacks(this);
displayWhiteBalanceController.setCallbacks(this);
} catch (Exception e) {
@@ -1025,10 +1029,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
-
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
@@ -1334,9 +1334,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
- }
+
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_STRONG_MODE;
+ msg.arg1 = isIdle ? 1 : 0;
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1405,8 +1407,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
}
+
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
}
+ // Call from handler thread
private void updatePowerState() {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"DisplayPowerController#updatePowerState");
@@ -2058,6 +2065,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ private void setDwbcOverride(float cct) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+ // The ambient color temperature override is only applied when the ambient color
+ // temperature changes or is updated, so it doesn't necessarily change the screen color
+ // temperature immediately. So, let's make it!
+ // We can call this directly, since we're already on the handler thread.
+ updatePowerState();
+ }
+ }
+
+ private void setDwbcStrongMode(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean isIdle = (arg == 1);
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
+ }
+
+ private void setDwbcLoggingEnabled(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean shouldEnable = (arg == 1);
+ mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+ }
+ }
+
@Override
public void updateBrightness() {
sendUpdatePowerState();
@@ -3331,6 +3364,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBootCompleted = true;
updatePowerState();
break;
+
+ case MSG_SET_DWBC_STRONG_MODE:
+ setDwbcStrongMode(msg.arg1);
+ break;
+
+ case MSG_SET_DWBC_COLOR_OVERRIDE:
+ final float cct = Float.intBitsToFloat(msg.arg1);
+ setDwbcOverride(cct);
+ break;
+
+ case MSG_SET_DWBC_LOGGING_ENABLED:
+ setDwbcLoggingEnabled(msg.arg1);
+ break;
}
}
}
@@ -3398,21 +3444,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+ msg.arg1 = enabled ? 1 : 0;
+ msg.sendToTarget();
}
@Override
public void setAmbientColorTemperatureOverride(float cct) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
- // The ambient color temperature override is only applied when the ambient color
- // temperature changes or is updated, so it doesn't necessarily change the screen color
- // temperature immediately. So, let's make it!
- sendUpdatePowerState();
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+ msg.arg1 = Float.floatToIntBits(cct);
+ msg.sendToTarget();
}
@VisibleForTesting
@@ -3543,6 +3586,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
hbmChangeCallback, hbmMetadata, context);
}
+
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return DisplayWhiteBalanceFactory.create(handler,
+ sensorManager, resources);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7043af863301..c2c0c0a496b6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -48,6 +48,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableFloat;
@@ -64,7 +65,6 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
@@ -73,6 +73,7 @@ import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -141,6 +142,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
private static final int MSG_SWITCH_USER = 12;
private static final int MSG_BOOT_COMPLETED = 13;
+ private static final int MSG_SET_DWBC_STRONG_MODE = 14;
+ private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
+ private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
@@ -367,6 +371,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private final boolean mSkipScreenOnBrightnessRamp;
// Display white balance components.
+ // Critical methods must be called on DPC2 handler thread.
@Nullable
private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
@Nullable
@@ -380,6 +385,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private final BrightnessThrottler mBrightnessThrottler;
+ private final BrightnessClamperController mBrightnessClamperController;
+
private final Runnable mOnBrightnessChangeRunnable;
private final BrightnessEvent mLastBrightnessEvent;
@@ -492,7 +499,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
- mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
@@ -554,16 +560,25 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler));
+
+ mBrightnessClamperController = new BrightnessClamperController(mHandler,
+ modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+ mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId,
+ mDisplayDeviceConfig
+ ));
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
+ mAutomaticBrightnessStrategy =
+ mDisplayBrightnessController.getAutomaticBrightnessStrategy();
DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
try {
+ displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
+ mHandler, mSensorManager, resources);
displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
- displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
- mSensorManager, resources);
displayWhiteBalanceSettings.setCallbacks(this);
displayWhiteBalanceController.setCallbacks(this);
} catch (Exception e) {
@@ -599,7 +614,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
setUpAutoBrightness(resources, handler);
- mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+ mColorFadeEnabled = mInjector.isColorFadeEnabled();
mColorFadeFadesConfig = resources.getBoolean(
R.bool.config_animateScreenLights);
@@ -782,6 +797,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+ mBrightnessClamperController.onDisplayChanged(
+ new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId, config));
+
mHandler.postAtTime(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -835,10 +854,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setEnabled(false);
- }
-
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
@@ -1149,9 +1164,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_STRONG_MODE;
+ msg.arg1 = isIdle ? 1 : 0;
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1187,6 +1203,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayPowerProximityStateController.cleanup();
mBrightnessRangeController.stop();
mBrightnessThrottler.stop();
+ mBrightnessClamperController.stop();
mHandler.removeCallbacksAndMessages(null);
// Release any outstanding wakelocks we're still holding because of pending messages.
@@ -1205,8 +1222,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
}
+
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setEnabled(false);
+ }
}
+ // Call from handler thread
private void updatePowerState() {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"DisplayPowerController#updatePowerState");
@@ -1257,14 +1279,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
int state = mDisplayStateController
.updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController
- .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
- && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
- && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
- }
-
// Initialize things the first time the power state is changed.
if (mustInitialize) {
initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
@@ -1290,6 +1304,17 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
slowChange = mBrightnessToFollowSlowChange;
}
+ // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+ // doesn't yet have a valid lux value to use with auto-brightness.
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+ && mIsEnabled && (state == Display.STATE_OFF
+ || (state == Display.STATE_DOZE
+ && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+ && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+ }
+
// Take note if the short term model was already active before applying the current
// request changes.
final boolean wasShortTermModelActive =
@@ -1519,6 +1544,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
+ animateValue = mBrightnessClamperController.clamp(animateValue);
+
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1563,7 +1590,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
- brightnessIsTemporary);
+ brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
@@ -1607,8 +1634,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
- .shouldUseAutoBrightness());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+ displayBrightnessState.getShouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1705,6 +1732,32 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
}
+ private void setDwbcOverride(float cct) {
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+ // The ambient color temperature override is only applied when the ambient color
+ // temperature changes or is updated, so it doesn't necessarily change the screen color
+ // temperature immediately. So, let's make it!
+ // We can call this directly, since we're already on the handler thread.
+ updatePowerState();
+ }
+ }
+
+ private void setDwbcStrongMode(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean isIdle = (arg == 1);
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
+ }
+
+ private void setDwbcLoggingEnabled(int arg) {
+ if (mDisplayWhiteBalanceController != null) {
+ final boolean enabled = (arg == 1);
+ mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+ mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+ }
+ }
+
@Override
public void updateBrightness() {
sendUpdatePowerState();
@@ -2219,7 +2272,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive, boolean autobrightnessEnabled,
- boolean brightnessIsTemporary) {
+ boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
final float brightnessInNits =
mDisplayBrightnessController.convertToAdjustedNits(brightness);
@@ -2234,7 +2287,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|| mAutomaticBrightnessController.isInIdleMode()
|| !autobrightnessEnabled
|| mBrightnessTracker == null
- || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ || !shouldUseAutoBrightness
|| brightnessInNits < 0.0f) {
return;
}
@@ -2411,6 +2464,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (mDisplayStateController != null) {
mDisplayStateController.dumpsys(pw);
}
+
+ pw.println();
+ if (mBrightnessClamperController != null) {
+ mBrightnessClamperController.dump(ipw);
+ }
}
@@ -2729,6 +2787,19 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBootCompleted = true;
updatePowerState();
break;
+
+ case MSG_SET_DWBC_STRONG_MODE:
+ setDwbcStrongMode(msg.arg1);
+ break;
+
+ case MSG_SET_DWBC_COLOR_OVERRIDE:
+ final float cct = Float.intBitsToFloat(msg.arg1);
+ setDwbcOverride(cct);
+ break;
+
+ case MSG_SET_DWBC_LOGGING_ENABLED:
+ setDwbcLoggingEnabled(msg.arg1);
+ break;
}
}
}
@@ -2779,21 +2850,18 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Override
public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
- mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+ msg.arg1 = enabled ? 1 : 0;
+ msg.sendToTarget();
}
@Override
public void setAmbientColorTemperatureOverride(float cct) {
- if (mDisplayWhiteBalanceController != null) {
- mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
- // The ambient color temperature override is only applied when the ambient color
- // temperature changes or is updated, so it doesn't necessarily change the screen color
- // temperature immediately. So, let's make it!
- sendUpdatePowerState();
- }
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+ msg.arg1 = Float.floatToIntBits(cct);
+ msg.sendToTarget();
}
/** Functional interface for providing time. */
@@ -2916,6 +2984,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
hbmChangeCallback, hbmMetadata, context);
}
+
+ boolean isColorFadeEnabled() {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
+
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return DisplayWhiteBalanceFactory.create(handler,
+ sensorManager, resources);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f836a5..26f8029cf5ac 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@ import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -142,6 +143,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
+ private final FoldSettingWrapper mFoldSettingWrapper;
private final PowerManager mPowerManager;
/**
@@ -189,21 +191,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler) {
+ @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
this(context, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++));
+ : sNextNonDefaultDisplayId++), foldSettingWrapper);
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+ @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+ FoldSettingWrapper foldSettingWrapper) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo;
mListener = listener;
+ mFoldSettingWrapper = foldSettingWrapper;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
* Returns if the device should be put to sleep or not.
*
* Includes a check to verify that the device state that we are moving to, {@code pendingState},
- * is the same as the physical state of the device, {@code baseState}. Different values for
- * these parameters indicate a device state override is active, and we shouldn't put the device
- * to sleep to provide a better user experience.
+ * is the same as the physical state of the device, {@code baseState}. Also if the
+ * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+ * state override is active, and we shouldn't put the device to sleep to provide a better user
+ * experience.
*
* @param pendingState device state we are moving to
* @param currentState device state we are currently in
@@ -551,7 +556,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
- && isInteractive && isBootCompleted;
+ && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
}
private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 2f52b708dfb5..ffd62a387a64 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -29,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import java.io.PrintWriter;
@@ -134,11 +135,21 @@ public final class DisplayBrightnessController {
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
+
+ DisplayBrightnessState state;
synchronized (mLock) {
mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, targetDisplayState);
- return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ state = mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ }
+
+ // This is a temporary measure until AutomaticBrightnessStrategy works as a traditional
+ // strategy.
+ // TODO: Remove when AutomaticBrightnessStrategy is populating the values directly.
+ if (state != null) {
+ state = addAutomaticBrightnessState(state);
}
+ return state;
}
/**
@@ -322,6 +333,13 @@ public final class DisplayBrightnessController {
}
/**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
+ }
+
+ /**
* Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
* applied. This is used when storing the brightness in nits for the default display and when
* passing the brightness value to follower displays.
@@ -425,6 +443,18 @@ public final class DisplayBrightnessController {
}
}
+ /**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
+ AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
+ builder.setShouldUseAutoBrightness(
+ autoStrat != null && autoStrat.shouldUseAutoBrightness());
+ return builder.build();
+ }
+
@GuardedBy("mLock")
private void setTemporaryBrightnessLocked(float temporaryBrightness) {
mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 02ca2d33fc99..45f1be076508 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -60,6 +61,8 @@ public class DisplayBrightnessStrategySelector {
private final FollowerBrightnessStrategy mFollowerBrightnessStrategy;
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+ // Controls brightness when automatic (adaptive) brightness is running.
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -81,6 +84,7 @@ public class DisplayBrightnessStrategySelector {
mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+ mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -130,6 +134,10 @@ public class DisplayBrightnessStrategySelector {
return mFollowerBrightnessStrategy;
}
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mAutomaticBrightnessStrategy;
+ }
+
/**
* Returns a boolean flag indicating if the light sensor is to be used to decide the screen
* brightness when dozing
@@ -198,5 +206,9 @@ public class DisplayBrightnessStrategySelector {
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return new InvalidBrightnessStrategy();
}
+
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+ return new AutomaticBrightnessStrategy(context, displayId);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
new file mode 100644
index 000000000000..9345a3d97122
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import java.io.PrintWriter;
+
+abstract class BrightnessClamper<T> {
+
+ protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ protected boolean mIsActive = false;
+
+ float getBrightnessCap() {
+ return mBrightnessCap;
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println("BrightnessClamper:" + getType());
+ writer.println(" mBrightnessCap: " + mBrightnessCap);
+ writer.println(" mIsActive: " + mIsActive);
+ }
+
+ @NonNull
+ abstract Type getType();
+
+ abstract void onDeviceConfigChanged();
+
+ abstract void onDisplayChanged(T displayData);
+
+ abstract void stop();
+
+ enum Type {
+ THERMAL
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
new file mode 100644
index 000000000000..d0f28c3bea81
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Clampers controller, all in DisplayControllerHandler
+ */
+public class BrightnessClamperController {
+
+ private static final boolean ENABLED = false;
+
+ private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
+ private final Handler mHandler;
+ private final ClamperChangeListener mClamperChangeListenerExternal;
+
+ private final Executor mExecutor;
+ private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ @Nullable
+ private Type mClamperType = null;
+
+ public BrightnessClamperController(Handler handler,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ this(new Injector(), handler, clamperChangeListener, data);
+ }
+
+ @VisibleForTesting
+ BrightnessClamperController(Injector injector, Handler handler,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mClamperChangeListenerExternal = clamperChangeListener;
+ mExecutor = new HandlerExecutor(handler);
+
+ Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+
+ ClamperChangeListener clamperChangeListenerInternal = () -> {
+ if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
+ mHandler.post(clamperChangeRunnableInternal);
+ }
+ };
+
+ if (ENABLED) {
+ mClampers.add(
+ new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
+ start();
+ }
+ }
+
+ /**
+ * Should be called when display changed. Forwards the call to individual clampers
+ */
+ public void onDisplayChanged(DisplayDeviceData data) {
+ mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+ }
+
+ /**
+ * Applies clamping
+ * Called in DisplayControllerHandler
+ */
+ public float clamp(float value) {
+ return Math.min(value, mBrightnessCap);
+ }
+
+ /**
+ * Used to dump ClampersController state.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("BrightnessClampersController:");
+ writer.println(" mBrightnessCap: " + mBrightnessCap);
+ writer.println(" mClamperType: " + mClamperType);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+ mClampers.forEach(clamper -> clamper.dump(ipw));
+ }
+
+ /**
+ * This method should be called when the ClamperController is no longer in use.
+ * Called in DisplayControllerHandler
+ */
+ public void stop() {
+ mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
+ mOnPropertiesChangedListener);
+ mClampers.forEach(BrightnessClamper::stop);
+ }
+
+
+ // Called in DisplayControllerHandler
+ private void recalculateBrightnessCap() {
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ Type clamperType = null;
+
+ BrightnessClamper<?> minClamper = mClampers.stream()
+ .filter(BrightnessClamper::isActive)
+ .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
+ clamper2.getBrightnessCap())).orElse(null);
+
+ if (minClamper != null) {
+ brightnessCap = minClamper.getBrightnessCap();
+ clamperType = minClamper.getType();
+ }
+
+ if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+ mBrightnessCap = brightnessCap;
+ mClamperType = clamperType;
+ mClamperChangeListenerExternal.onChanged();
+ }
+ }
+
+ private void start() {
+ mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+ mExecutor, mOnPropertiesChangedListener);
+ }
+
+ /**
+ * Clampers change listener
+ */
+ public interface ClamperChangeListener {
+ /**
+ * Notifies that clamper state changed
+ */
+ void onChanged();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+
+ /**
+ * Data for clampers
+ */
+ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+ @NonNull
+ private final String mUniqueDisplayId;
+ @NonNull
+ private final String mThermalThrottlingDataId;
+
+ private final DisplayDeviceConfig mDisplayDeviceConfig;
+
+ public DisplayDeviceData(@NonNull String uniqueDisplayId,
+ @NonNull String thermalThrottlingDataId,
+ @NonNull DisplayDeviceConfig displayDeviceConfig) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mThermalThrottlingDataId = thermalThrottlingDataId;
+ mDisplayDeviceConfig = displayDeviceConfig;
+ }
+
+
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getThermalThrottlingDataId() {
+ return mThermalThrottlingDataId;
+ }
+
+ @Nullable
+ @Override
+ public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+ return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
+ mThermalThrottlingDataId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
new file mode 100644
index 000000000000..8ae962b83c69
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessThermalClamper extends
+ BrightnessClamper<BrightnessThermalClamper.ThermalData> {
+
+ private static final String TAG = "BrightnessThermalClamper";
+
+ @Nullable
+ private final IThermalService mThermalService;
+ @NonNull
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final ClamperChangeListener mChangelistener;
+ // data from DeviceConfig, for all displays, for all dataSets
+ // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
+ @NonNull
+ private Map<String, Map<String, ThermalBrightnessThrottlingData>>
+ mThermalThrottlingDataOverride = Map.of();
+ // data from DisplayDeviceConfig, for particular display+dataSet
+ @Nullable
+ private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null;
+ // Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it,
+ // otherwise mDataFromDeviceConfig
+ @Nullable
+ private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
+ private boolean mStarted = false;
+ @Nullable
+ private String mUniqueDisplayId = null;
+ @Nullable
+ private String mDataId = null;
+ @Temperature.ThrottlingStatus
+ private int mThrottlingStatus = Temperature.THROTTLING_NONE;
+
+ private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
+ @Override
+ public void notifyThrottling(Temperature temperature) {
+ @Temperature.ThrottlingStatus int status = temperature.getStatus();
+ mHandler.post(() -> thermalStatusChanged(status));
+ }
+ };
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+ return new ThrottlingLevel(status, brightnessPoint);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+ mDataSetMapper = ThermalBrightnessThrottlingData::create;
+
+
+ BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
+ ThermalData thermalData) {
+ this(new Injector(), handler, listener, thermalData);
+ }
+
+ @VisibleForTesting
+ BrightnessThermalClamper(Injector injector, Handler handler,
+ ClamperChangeListener listener, ThermalData thermalData) {
+ mThermalService = injector.getThermalService();
+ mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mChangelistener = listener;
+ mHandler.post(() -> {
+ setDisplayData(thermalData);
+ loadOverrideData();
+ start();
+ });
+
+ }
+
+ @Override
+ @NonNull
+ Type getType() {
+ return Type.THERMAL;
+ }
+
+ @Override
+ void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void onDisplayChanged(ThermalData data) {
+ mHandler.post(() -> {
+ setDisplayData(data);
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void stop() {
+ if (!mStarted) {
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(mThermalEventListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mStarted = false;
+ }
+
+ @Override
+ void dump(PrintWriter writer) {
+ writer.println("BrightnessThermalClamper:");
+ writer.println(" mStarted: " + mStarted);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
+ writer.println(" mThrottlingStatus: " + mThrottlingStatus);
+ writer.println(" mUniqueDisplayId: " + mUniqueDisplayId);
+ writer.println(" mDataId: " + mDataId);
+ writer.println(" mDataOverride: " + mThermalThrottlingDataOverride);
+ writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
+ writer.println(" mDataActive: " + mThermalThrottlingDataActive);
+ super.dump(writer);
+ }
+
+ private void recalculateActiveData() {
+ if (mUniqueDisplayId == null || mDataId == null) {
+ return;
+ }
+ mThermalThrottlingDataActive = mThermalThrottlingDataOverride
+ .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+ mThermalThrottlingDataFromDeviceConfig);
+
+ recalculateBrightnessCap();
+ }
+
+ private void loadOverrideData() {
+ String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData();
+ mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+ throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+ }
+
+ private void setDisplayData(@NonNull ThermalData data) {
+ mUniqueDisplayId = data.getUniqueDisplayId();
+ mDataId = data.getThermalThrottlingDataId();
+ mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData();
+ if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) {
+ Slog.wtf(TAG,
+ "Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
+ }
+ }
+
+ private void recalculateBrightnessCap() {
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ boolean isActive = false;
+
+ if (mThermalThrottlingDataActive != null) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
+ if (level.thermalStatus <= mThrottlingStatus) {
+ brightnessCap = level.brightness;
+ isActive = true;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+
+ if (brightnessCap != mBrightnessCap || mIsActive != isActive) {
+ mBrightnessCap = brightnessCap;
+ mIsActive = isActive;
+ mChangelistener.onChanged();
+ }
+ }
+
+ private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) {
+ if (mThrottlingStatus != status) {
+ mThrottlingStatus = status;
+ recalculateBrightnessCap();
+ }
+ }
+
+ private void start() {
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
+ Temperature.TYPE_SKIN);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ interface ThermalData {
+ @NonNull
+ String getUniqueDisplayId();
+
+ @NonNull
+ String getThermalThrottlingDataId();
+
+ @Nullable
+ ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
new file mode 100644
index 000000000000..a8034c58689d
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PowerManager;
+import android.util.Slog;
+
+import com.android.server.display.DisplayDeviceConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Provides utility methods for DeviceConfig string parsing
+ */
+public class DeviceConfigParsingUtils {
+ private static final String TAG = "DeviceConfigParsingUtils";
+
+ /**
+ * Parses map from device config
+ * Data format:
+ * (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
+ * dataSetId:String)?;)+
+ * result : mapOf(displayId to mapOf(dataSetId to V))
+ */
+ @NonNull
+ public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
+ @Nullable String data,
+ @NonNull BiFunction<String, String, T> dataPointMapper,
+ @NonNull Function<List<T>, V> dataSetMapper) {
+ if (data == null) {
+ return Map.of();
+ }
+ Map<String, Map<String, V>> result = new HashMap<>();
+ String[] dataSets = data.split(";"); // by displayId + dataSetId
+ for (String dataSet : dataSets) {
+ String[] items = dataSet.split(",");
+ int noOfItems = items.length;
+ // Validate number of items, at least: displayId,1,key1,value1
+ if (noOfItems < 4) {
+ Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
+ return Map.of();
+ }
+ int i = 0;
+ String uniqueDisplayId = items[i++];
+
+ String numberOfPointsString = items[i++];
+ int numberOfPoints;
+ try {
+ numberOfPoints = Integer.parseInt(numberOfPointsString);
+ } catch (NumberFormatException nfe) {
+ Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
+ return Map.of();
+ }
+ // Validate number of itmes based on numberOfPoints:
+ // displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
+ int expectedMinItems = 2 + numberOfPoints * 2;
+ if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
+ Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
+ return Map.of();
+ }
+ // Construct data points
+ List<T> dataPoints = new ArrayList<>();
+ for (int j = 0; j < numberOfPoints; j++) {
+ String key = items[i++];
+ String value = items[i++];
+ T dataPoint = dataPointMapper.apply(key, value);
+ if (dataPoint == null) {
+ Slog.e(TAG,
+ "Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
+ + dataSet, new Throwable());
+ return Map.of();
+ }
+ dataPoints.add(dataPoint);
+ }
+ // Construct dataSet
+ V dataSetMapped = dataSetMapper.apply(dataPoints);
+ if (dataSetMapped == null) {
+ Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
+ + dataSet, new Throwable());
+ return Map.of();
+ }
+ // Get dataSetId and dataSets map for displayId
+ String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
+ Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
+ k -> new HashMap<>());
+
+ // Try to store dataSet in datasets for display
+ if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
+ Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
+ return Map.of();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Parses thermal string value from device config
+ */
+ @PowerManager.ThermalStatus
+ public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
+ switch (value) {
+ case "none":
+ return PowerManager.THERMAL_STATUS_NONE;
+ case "light":
+ return PowerManager.THERMAL_STATUS_LIGHT;
+ case "moderate":
+ return PowerManager.THERMAL_STATUS_MODERATE;
+ case "severe":
+ return PowerManager.THERMAL_STATUS_SEVERE;
+ case "critical":
+ return PowerManager.THERMAL_STATUS_CRITICAL;
+ case "emergency":
+ return PowerManager.THERMAL_STATUS_EMERGENCY;
+ case "shutdown":
+ return PowerManager.THERMAL_STATUS_SHUTDOWN;
+ default:
+ throw new IllegalArgumentException("Invalid Thermal Status: " + value);
+ }
+ }
+
+ /**
+ * Parses brightness value from device config
+ */
+ public static float parseBrightness(String stringVal) throws IllegalArgumentException {
+ float value = Float.parseFloat(stringVal);
+ if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+ throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
+ }
+ return value;
+ }
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 5b772fc917c5..4ad26c46d7ed 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -37,8 +37,11 @@ import java.util.Objects;
* - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature;
* - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the
* noise, and arrive at an estimate of the actual ambient color temperature;
- * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should
+ * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color temperature should
* be updated, suppressing changes that are too frequent or too minor.
+ *
+ * Calls to this class must happen on the DisplayPowerController(2) handler, to ensure
+ * values do not get out of sync.
*/
public class DisplayWhiteBalanceController implements
AmbientSensor.AmbientBrightnessSensor.Callbacks,
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 63fded1a35fe..7a8de341854d 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
@@ -1272,7 +1273,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
- noLayoutFound ? LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
: layoutInfo.mSelectionCriteria);
}
KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 19fa7a8e0aca..eb2da340004b 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -53,7 +53,8 @@ public final class KeyboardMetricsCollector {
@IntDef(prefix = { "LAYOUT_SELECTION_CRITERIA_" }, value = {
LAYOUT_SELECTION_CRITERIA_USER,
LAYOUT_SELECTION_CRITERIA_DEVICE,
- LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ LAYOUT_SELECTION_CRITERIA_DEFAULT
})
public @interface LayoutSelectionCriteria {}
@@ -66,9 +67,15 @@ public final class KeyboardMetricsCollector {
/** Auto-detection based on IME provided language tag and layout type */
public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+ /** Default selection */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+
@VisibleForTesting
static final String DEFAULT_LAYOUT = "Default";
+ @VisibleForTesting
+ static final String DEFAULT_LANGUAGE_TAG = "None";
+
/**
* Log keyboard system shortcuts for the proto
* {@link com.android.os.input.KeyboardSystemsEventReported}
@@ -131,6 +138,10 @@ public final class KeyboardMetricsCollector {
layoutConfiguration.keyboardLayoutName);
proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
layoutConfiguration.layoutSelectionCriteria);
+ proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+ layoutConfiguration.imeLanguageTag);
+ proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+ layoutConfiguration.imeLayoutType);
proto.end(keyboardLayoutConfigToken);
}
@@ -231,27 +242,29 @@ public final class KeyboardMetricsCollector {
@LayoutSelectionCriteria int layoutSelectionCriteria =
mLayoutSelectionCriteriaList.get(i);
InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
- String keyboardLanguageTag;
- String keyboardLayoutStringType;
- if (layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) {
- keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
- keyboardLayoutStringType = mInputDevice.getKeyboardLayoutType();
- } else {
- ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
- keyboardLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
- : imeSubtype.getCanonicalizedLanguageTag();
- keyboardLayoutStringType = imeSubtype.getPhysicalKeyboardHintLayoutType();
- }
+ String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
+ keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
+ : keyboardLanguageTag;
+ int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ mInputDevice.getKeyboardLayoutType());
+
+ ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
+ String canonicalizedLanguageTag =
+ imeSubtype.getCanonicalizedLanguageTag().equals("")
+ ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
+ String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
+ : canonicalizedLanguageTag;
+ int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ imeSubtype.getPhysicalKeyboardHintLayoutType());
+
// Sanitize null values
String keyboardLayoutName =
selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
- keyboardLanguageTag = keyboardLanguageTag == null ? "" : keyboardLanguageTag;
- int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
- keyboardLayoutStringType);
configurationList.add(
new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
- keyboardLayoutName, layoutSelectionCriteria));
+ keyboardLayoutName, layoutSelectionCriteria,
+ imeLayoutType, imeLanguageTag));
}
return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
configurationList);
@@ -267,13 +280,18 @@ public final class KeyboardMetricsCollector {
public final String keyboardLayoutName;
@LayoutSelectionCriteria
public final int layoutSelectionCriteria;
+ public final int imeLayoutType;
+ public final String imeLanguageTag;
private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
- String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria) {
+ String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
+ int imeLayoutType, String imeLanguageTag) {
this.keyboardLayoutType = keyboardLayoutType;
this.keyboardLanguageTag = keyboardLanguageTag;
this.keyboardLayoutName = keyboardLayoutName;
this.layoutSelectionCriteria = layoutSelectionCriteria;
+ this.imeLayoutType = imeLayoutType;
+ this.imeLanguageTag = imeLanguageTag;
}
@Override
@@ -281,7 +299,9 @@ public final class KeyboardMetricsCollector {
return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+ KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
+ " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
- + getStringForSelectionCriteria(layoutSelectionCriteria) + "}";
+ + getStringForSelectionCriteria(layoutSelectionCriteria)
+ + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
+ + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
}
}
@@ -294,6 +314,8 @@ public final class KeyboardMetricsCollector {
return "LAYOUT_SELECTION_CRITERIA_DEVICE";
case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+ case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+ return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
default:
return "INVALID_CRITERIA";
}
@@ -302,7 +324,8 @@ public final class KeyboardMetricsCollector {
private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
|| layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
- || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a96e4adf1fee..0616f4e9d5ac 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -844,6 +844,10 @@ public class LockSettingsService extends ILockSettings.Stub {
getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady();
+ // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the
+ // first invalidation. This can be removed if PropertyInvalidatedCache is fixed.
+ LockPatternUtils.invalidateCredentialTypeCache();
+
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3325ddd58330..6df38098205a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -322,7 +322,6 @@ import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.Slogf;
@@ -1715,7 +1714,6 @@ public class NotificationManagerService extends SystemService {
return;
}
- boolean queryRestart = false;
boolean queryRemove = false;
boolean packageChanged = false;
boolean cancelNotifications = true;
@@ -1727,7 +1725,6 @@ public class NotificationManagerService extends SystemService {
|| (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
- || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
@@ -1768,10 +1765,6 @@ public class NotificationManagerService extends SystemService {
cancelNotifications = false;
unhideNotifications = true;
}
-
- } else if (queryRestart) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
} else {
Uri uri = intent.getData();
if (uri == null) {
@@ -1809,7 +1802,7 @@ public class NotificationManagerService extends SystemService {
if (cancelNotifications) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
- !queryRestart, changeUserId, reason, null);
+ changeUserId, reason);
}
} else if (hideNotifications && uidList != null && (uidList.length > 0)) {
hideNotificationsForPackages(pkgList, uidList);
@@ -1843,14 +1836,14 @@ public class NotificationManagerService extends SystemService {
} else if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_USER_STOPPED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_USER_STOPPED);
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_PROFILE_TURNED_OFF, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_PROFILE_TURNED_OFF);
mSnoozeHelper.clearData(userHandle);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -2468,7 +2461,6 @@ public class NotificationManagerService extends SystemService {
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
pkgFilter.addDataScheme("package");
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
null);
@@ -2499,6 +2491,16 @@ public class NotificationManagerService extends SystemService {
getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
ReviewNotificationPermissionsReceiver.getFilter(),
Context.RECEIVER_NOT_EXPORTED);
+
+ mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
+ new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(@NonNull String op, @NonNull String packageName,
+ int userId) {
+ mHandler.post(
+ () -> handleNotificationPermissionChange(packageName, userId));
+ }
+ });
}
/**
@@ -2855,17 +2857,17 @@ public class NotificationManagerService extends SystemService {
boolean fromListener) {
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
// cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
+ );
if (isUidSystemOrPhone(uid)) {
IntArray profileIds = mUserProfiles.getCurrentProfileIds();
int N = profileIds.size();
for (int i = 0; i < N; i++) {
int profileId = profileIds.get(i);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- profileId, REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ profileId, REASON_CHANNEL_BANNED
+ );
}
}
}
@@ -3539,7 +3541,7 @@ public class NotificationManagerService extends SystemService {
// Don't allow the app to cancel active FGS or UIJ notifications
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
- true, userId, REASON_APP_CANCEL_ALL, null);
+ userId, REASON_APP_CANCEL_ALL);
}
@Override
@@ -3558,20 +3560,16 @@ public class NotificationManagerService extends SystemService {
}
mPermissionHelper.setNotificationPermission(
pkg, UserHandle.getUserId(uid), enabled, true);
- sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.setSubtype(enabled ? 1 : 0));
mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
- // Now, cancel any outstanding notifications that are part of a just-disabled app
- if (!enabled) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
- UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
- }
- handleSavePolicyFile();
+ // Outstanding notifications from this package will be cancelled, and the package will
+ // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the
+ // callback from AppOpsManager.
}
/**
@@ -4030,8 +4028,8 @@ public class NotificationManagerService extends SystemService {
}
enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- callingUser, REASON_CHANNEL_REMOVED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ callingUser, REASON_CHANNEL_REMOVED);
boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
if (previouslyExisted) {
@@ -4085,9 +4083,8 @@ public class NotificationManagerService extends SystemService {
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
- true,
- userId, REASON_CHANNEL_REMOVED,
- null);
+ userId, REASON_CHANNEL_REMOVED
+ );
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
deletedChannel,
@@ -4256,8 +4253,8 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystem();
// Cancel posted notifications
final int userId = UserHandle.getUserId(uid);
- cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA);
// Zen
packagesChanged |=
@@ -5895,6 +5892,21 @@ public class NotificationManagerService extends SystemService {
}
};
+ private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+ int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+ if (uid == INVALID_UID) {
+ Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+ return;
+ }
+ boolean hasPermission = mPermissionHelper.hasPermission(uid);
+ sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission);
+ if (!hasPermission) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+ /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+ REASON_PACKAGE_BANNED);
+ }
+ }
+
protected void checkNotificationListenerAccess() {
if (!isCallerSystemOrPhone()) {
getContext().enforceCallingPermission(
@@ -6831,9 +6843,9 @@ public class NotificationManagerService extends SystemService {
mPreferencesHelper.deleteConversations(pkg, uid, shortcuts,
/* callingUid */ Process.SYSTEM_UID, /* is system */ true);
for (String channelId : deletedChannelIds) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED
+ );
}
handleSavePolicyFile();
}
@@ -9535,25 +9547,18 @@ public class NotificationManagerService extends SystemService {
/**
* Cancels all notifications from a given package that have all of the
- * {@code mustHaveFlags}.
+ * {@code mustHaveFlags} and none of the {@code mustNotHaveFlags}.
*/
- void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
- int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
- ManagedServiceInfo listener) {
+ void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg,
+ @Nullable String channelId, int mustHaveFlags, int mustNotHaveFlags, int userId,
+ int reason) {
final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
mHandler.post(new Runnable() {
@Override
public void run() {
- String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listenerName);
-
- // Why does this parameter exist? Do we actually want to execute the above if doit
- // is false?
- if (!doit) {
- return;
- }
+ /* listener= */ null);
synchronized (mNotificationLock) {
FlagChecker flagChecker = (int flags) -> {
@@ -9565,14 +9570,15 @@ public class NotificationManagerService extends SystemService {
}
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
- pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ cancelAllNotificationsByListLocked(mNotificationList, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+ null /* listenerName */, true /* wasPosted */,
+ cancellationElapsedTimeMs);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
- listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
- flagChecker, false /*includeCurrentProfiles*/, userId,
- false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+ null /* listenerName */, false /* wasPosted */,
cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, pkg);
}
@@ -9587,9 +9593,9 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
- int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
- String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
- boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+ @Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
+ FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, boolean sendDelete,
+ int reason, String listenerName, boolean wasPosted,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
Set<String> childNotifications = null;
for (int i = notificationList.size() - 1; i >= 0; --i) {
@@ -9706,12 +9712,12 @@ public class NotificationManagerService extends SystemService {
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ cancelAllNotificationsByListLocked(mNotificationList,
null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
includeCurrentProfiles, userId, true /*sendDelete*/, reason,
listenerName, true, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications,
+ null, false /*nullPkgIndicatesUserSwitch*/, null,
flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
reason, listenerName, false, cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, includeCurrentProfiles);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 27329e20bc8d..6821c40ec5d3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16244,7 +16244,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
NP = in.readInt();
- if (NP > 1000) {
+ if (NP > 10000) {
throw new ParcelFormatException("File corrupt: too many processes " + NP);
}
for (int ip = 0; ip < NP; ip++) {
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 000000000000..97a1ac06e24c
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+ private final ContentResolver mContentResolver;
+
+ public FoldSettingWrapper(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns whether the device should remain awake after folding.
+ */
+ public boolean shouldStayAwakeOnFold() {
+ try {
+ return (Settings.System.getIntForUser(
+ mContentResolver,
+ Settings.System.STAY_AWAKE_ON_FOLD,
+ 0) == 1);
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0994fa4464db..dd6bcb1060ea 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5660,13 +5660,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final DisplayContent displayContent = getDisplayContent();
if (!visible) {
mImeInsetsFrozenUntilStartInput = true;
- if (usingShellTransitions) {
- final WindowState wallpaperTarget =
- displayContent.mWallpaperController.getWallpaperTarget();
- if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
- displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
- }
- }
}
if (!displayContent.mClosingApps.contains(this)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4262e94e2894..884100c5da33 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1591,6 +1591,9 @@ class ActivityStarter {
if (forceTransientTransition) {
transitionController.collect(mLastStartActivityRecord);
transitionController.collect(mPriorAboveTask);
+ // If keyguard is active and occluded, the transient target won't be moved to front
+ // to be collected, so set transient again after it is collected.
+ transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
// update wallpaper target to TransientHide
dc.mWallpaperController.adjustWallpaperWindows();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 738797b809a5..50948e1cdec4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2186,7 +2186,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
* Processes the activities to be stopped or destroyed. This should be called when the resumed
* activities are idle or drawn.
*/
- private void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
+ void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
boolean processPausingActivities, String reason) {
// Stop any activities that are scheduled to do so but have been waiting for the transition
// animation to finish.
@@ -2194,7 +2194,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
ArrayList<ActivityRecord> readyToStopActivities = null;
for (int i = 0; i < mStoppingActivities.size(); i++) {
final ActivityRecord s = mStoppingActivities.get(i);
- final boolean animating = s.isInTransition();
+ // Activity in a force hidden task should not be counted as animating, i.e., we want to
+ // send onStop before any configuration change when removing pip transition is ongoing.
+ final boolean animating = s.isInTransition()
+ && s.getTask() != null && !s.getTask().isForceHidden();
displaySwapping |= s.isDisplaySleepingAndSwapping();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ "finishing=%s", s, s.nowVisible, animating, s.finishing);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 01158779c24f..4ce21bdadf49 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -93,15 +93,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
- /** Whether all windows should wait for the start transaction. */
- private boolean mAlwaysWaitForStartTransaction;
-
/** Whether the target windows have been requested to sync their draw transactions. */
private boolean mIsSyncDrawRequested;
private SeamlessRotator mRotator;
- private final int mOriginalRotation;
+ private int mOriginalRotation;
private final boolean mHasScreenRotationAnimation;
AsyncRotationController(DisplayContent displayContent) {
@@ -147,15 +144,6 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (mTransitionOp == OP_LEGACY) {
mIsStartTransactionCommitted = true;
} else if (displayContent.mTransitionController.isCollecting(displayContent)) {
- final Transition transition =
- mDisplayContent.mTransitionController.getCollectingTransition();
- if (transition != null) {
- final BLASTSyncEngine.SyncGroup syncGroup =
- mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
- if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
- mAlwaysWaitForStartTransaction = true;
- }
- }
keepAppearanceInPreviousRotation();
}
}
@@ -279,10 +267,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// The previous animation leash will be dropped when preparing fade-in animation, so
// simply apply new animation without restoring the transformation.
fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
- } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
+ } else if (op.mAction == Operation.ACTION_SEAMLESS
&& op.mLeash != null && op.mLeash.isValid()) {
if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
- mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
+ final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
+ t.setMatrix(op.mLeash, 1, 0, 0, 1);
+ t.setPosition(op.mLeash, 0, 0);
}
}
@@ -365,6 +355,32 @@ class AsyncRotationController extends FadeAnimationController implements Consume
}
}
+ /**
+ * Re-initialize the states if the current display rotation has changed to a different rotation.
+ * This is mainly for seamless rotation to update the transform based on new rotation.
+ */
+ void updateRotation() {
+ if (mRotator == null) return;
+ final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation();
+ if (mOriginalRotation == currentRotation) {
+ return;
+ }
+ Slog.d(TAG, "Update original rotation " + currentRotation);
+ mOriginalRotation = currentRotation;
+ mDisplayContent.forAllWindows(w -> {
+ if (w.mForceSeamlesslyRotate && w.mHasSurface
+ && !mTargetWindowTokens.containsKey(w.mToken)) {
+ final Operation op = new Operation(Operation.ACTION_SEAMLESS);
+ op.mLeash = w.mToken.mSurfaceControl;
+ mTargetWindowTokens.put(w.mToken, op);
+ }
+ }, true /* traverseTopToBottom */);
+ mRotator = null;
+ mIsStartTransactionCommitted = false;
+ mIsSyncDrawRequested = false;
+ keepAppearanceInPreviousRotation();
+ }
+
private void scheduleTimeout() {
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
@@ -589,7 +605,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
* start transaction of rotation transition is applied.
*/
private boolean canDrawBeforeStartTransaction(Operation op) {
- return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+ return op.mAction != Operation.ACTION_SEAMLESS;
}
/** The operation to control the rotation appearance associated with window token. */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bfd2a10a8882..cb7414e2e86e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3457,6 +3457,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
this, this, null /* remoteTransition */, displayChange);
if (t != null) {
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+ if (mAsyncRotationController != null) {
+ // Give a chance to update the transform if the current rotation is changed when
+ // some windows haven't finished previous rotation.
+ mAsyncRotationController.updateRotation();
+ }
if (mFixedRotationLaunchingApp != null) {
// A fixed-rotation transition is done, then continue to start a seamless display
// transition.
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7a201a77c966..e945bc1babd9 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -294,16 +294,15 @@ final class LetterboxConfiguration {
@NonNull private final SynchedDeviceConfig mDeviceConfig;
LetterboxConfiguration(@NonNull final Context systemUiContext) {
- this(systemUiContext,
- new LetterboxConfigurationPersister(systemUiContext,
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ false),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ false),
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ true),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ true)));
+ this(systemUiContext, new LetterboxConfigurationPersister(
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ false),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ false),
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ true),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ true)));
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 756339701590..38aa903e3954 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.Environment;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
@@ -53,10 +52,8 @@ class LetterboxConfigurationPersister {
private static final String TAG =
TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
- @VisibleForTesting
- static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+ private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
- private final Context mContext;
private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
@@ -97,36 +94,32 @@ class LetterboxConfigurationPersister {
@NonNull
private final PersisterQueue mPersisterQueue;
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
- this(systemUiContext, defaultHorizontalReachabilitySupplier,
- defaultVerticalReachabilitySupplier,
- defaultBookModeReachabilitySupplier,
- defaultTabletopModeReachabilitySupplier,
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
+ this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
+ defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
Environment.getDataSystemDirectory(), new PersisterQueue(),
- /* completionCallback */ null);
+ /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
}
@VisibleForTesting
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier,
- File configFolder,
- PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
- mContext = systemUiContext.createDeviceProtectedStorageContext();
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+ @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
+ @Nullable Consumer<String> completionCallback,
+ @NonNull String letterboxConfigurationFileName) {
mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
- mDefaultBookModeReachabilitySupplier =
- defaultBookModeReachabilitySupplier;
- mDefaultTabletopModeReachabilitySupplier =
- defaultTabletopModeReachabilitySupplier;
+ mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
+ mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
mCompletionCallback = completionCallback;
- final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
mConfigurationFile = new AtomicFile(prefFiles);
mPersisterQueue = persisterQueue;
runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d9a954f1973b..05f95f813e55 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3212,6 +3212,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
+ "not idle", rootTask.getRootTaskId(), resumedActivity);
return false;
}
+ if (mTransitionController.isTransientLaunch(resumedActivity)) {
+ // Not idle if the transient transition animation is running.
+ return false;
+ }
}
// End power mode launch when idle.
mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0c7d007dc48a..71192cd5a3be 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1191,16 +1191,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
hasParticipatedDisplay = true;
continue;
}
- final WallpaperWindowToken wt = participant.asWallpaperToken();
- if (wt != null) {
- final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
- if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit wallpaper becoming invisible: %s", wt);
- wt.commitVisibility(false /* visible */);
- }
- continue;
- }
final Task tr = participant.asTask();
if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
final ActivityRecord top = tr.getTopNonFinishingActivity();
@@ -1220,6 +1210,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
}
+ // Commit wallpaper visibility after activity, because usually the wallpaper target token is
+ // an activity, and wallpaper's visibility is depends on activity's visibility.
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ if (wt == null) continue;
+ final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
+ final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
+ if (isTargetInvisible || (!wt.isVisibleRequested()
+ && !mVisibleAtTransitionEndTokens.contains(wt))) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit wallpaper becoming invisible: %s", wt);
+ wt.commitVisibility(false /* visible */);
+ }
+ }
if (committedSomeInvisible) {
mController.onCommittedInvisibles();
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4ce150e03558..541fa9413f45 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -569,6 +569,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
if (forceHiddenForPip) {
wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
+ // When removing pip, make sure that onStop is sent to the app ahead of
+ // onPictureInPictureModeChanged.
+ // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
+ wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
+ null /* launchedActivity */, false /* processPausingActivities */,
+ "force-stop-on-removing-pip");
}
int containerEffect = applyWindowContainerChange(wc, entry.getValue(),
diff --git a/services/flags/Android.bp b/services/flags/Android.bp
new file mode 100644
index 000000000000..2d0337dce74f
--- /dev/null
+++ b/services/flags/Android.bp
@@ -0,0 +1,17 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_static {
+ name: "services.flags",
+ defaults: ["platform_service_defaults"],
+ srcs: [
+ "java/**/*.java",
+ ],
+ libs: ["services.core"],
+}
diff --git a/services/flags/OWNERS b/services/flags/OWNERS
new file mode 100644
index 000000000000..3925b5c13c2d
--- /dev/null
+++ b/services/flags/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1306523
+
+mankoff@google.com
+
+pixel@google.com
+dsandler@android.com
diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
new file mode 100644
index 000000000000..0db328792cf3
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
@@ -0,0 +1,280 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Handles DynamicFlags for {@link FeatureFlagsBinder}.
+ *
+ * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can
+ * return whatever value is last known for a flag is, without too much worry about the flags
+ * changing (they are dynamic after all). However, we have to alert all the relevant clients
+ * about those flag changes, and need to be able to restore to a default value if the flag gets
+ * reset/erased during runtime.
+ */
+class DynamicFlagBinderDelegate {
+
+ private final FlagOverrideStore mFlagStore;
+ private final FlagCache<DynamicFlagData> mDynamicFlags = new FlagCache<>();
+ private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
+ private static final Function<Integer, Set<IFeatureFlagsCallback>> NEW_CALLBACK_SET =
+ k -> new HashSet<>();
+
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ String ns = properties.getNamespace();
+ for (String name : properties.getKeyset()) {
+ // Don't alert for flags we don't care about.
+ // Don't alert for flags that have been overridden locally.
+ if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) {
+ continue;
+ }
+ mFlagChangeCallback.onFlagChanged(
+ ns, name, properties.getString(name, null));
+ }
+ }
+ };
+
+ private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback =
+ (namespace, name, value) -> {
+ // Don't bother with callbacks for non-dynamic flags.
+ if (!mDynamicFlags.contains(namespace, name)) {
+ return;
+ }
+
+ // Don't bother with callbacks if nothing changed.
+ // Handling erasure (null) is special, as we may be restoring back to a value
+ // we were already at.
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data == null) {
+ return; // shouldn't happen, but better safe than sorry.
+ }
+ if (value == null) {
+ if (data.getValue().equals(data.getDefaultValue())) {
+ return;
+ }
+ value = data.getDefaultValue();
+ } else if (data.getValue().equals(value)) {
+ return;
+ }
+ data.setValue(value);
+
+ final Set<IFeatureFlagsCallback> cbCopy;
+ synchronized (mCallbacks) {
+ cbCopy = new HashSet<>();
+
+ for (Integer pid : mCallbacks.keySet()) {
+ if (data.containsPid(pid)) {
+ cbCopy.addAll(mCallbacks.get(pid));
+ }
+ }
+ }
+ SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true);
+ cbCopy.forEach(cb -> {
+ try {
+ cb.onFlagChange(sFlag);
+ } catch (RemoteException e) {
+ Slog.w(
+ FeatureFlagsService.TAG,
+ "Failed to communicate flag change to client.");
+ }
+ });
+ };
+
+ DynamicFlagBinderDelegate(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ mFlagStore.setChangeCallback(mFlagChangeCallback);
+ }
+
+ SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) {
+ if (!sf.isDynamic()) {
+ return sf;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+
+ // Dynamic flags don't need any special threading or synchronization considerations.
+ // We simply give them whatever the current value is.
+ // However, we do need to keep track of dynamic flags, so that we can alert
+ // about changes coming in from adb, DeviceConfig, or other sources.
+ // And also so that we can keep flags relatively consistent across processes.
+
+ DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
+ String value = getFlagValue(ns, name, sf.getValue());
+ // DeviceConfig listeners are per-namespace.
+ if (!mDynamicFlags.containsNamespace(ns)) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ ns, BackgroundThread.getExecutor(), mDeviceConfigListener);
+ }
+ data.addClientPid(pid);
+ data.setValue(value);
+ // Store the default value so that if an override gets erased, we can restore
+ // to something.
+ data.setDefaultValue(sf.getValue());
+
+ return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true);
+ }
+
+
+ void registerCallback(int pid, IFeatureFlagsCallback callback) {
+ // Always add callback so that we don't end up with a possible race/leak.
+ // We remove the callback directly if we fail to call #linkToDeath.
+ // If we tried to add the callback after we linked, then we could end up in a
+ // scenario where we link, then the binder dies, firing our BinderGriever which tries
+ // to remove the callback (which has not yet been added), then finally we add the
+ // callback, creating a leak.
+ Set<IFeatureFlagsCallback> callbacks;
+ synchronized (mCallbacks) {
+ callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.add(callback);
+ }
+ try {
+ callback.asBinder().linkToDeath(new BinderGriever(pid), 0);
+ } catch (RemoteException e) {
+ Slog.e(
+ FeatureFlagsService.TAG,
+ "Failed to link to binder death. Callback not registered.");
+ synchronized (mCallbacks) {
+ callbacks.remove(callback);
+ }
+ }
+ }
+
+ void unregisterCallback(int pid, IFeatureFlagsCallback callback) {
+ // No need to unlink, since the BinderGriever will essentially be a no-op.
+ // We would have to track our BinderGriever's in a map otherwise.
+ synchronized (mCallbacks) {
+ Set<IFeatureFlagsCallback> callbacks =
+ mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.remove(callback);
+ }
+ }
+
+ String getFlagValue(String namespace, String name, String defaultValue) {
+ // If we already have a value cached, just use that.
+ String value = null;
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data != null) {
+ value = data.getValue();
+ } else {
+ // Put the value in the cache for future reference.
+ data = new DynamicFlagData(namespace, name);
+ mDynamicFlags.setIfChanged(namespace, name, data);
+ }
+ // If we're not in a release build, flags can be overridden locally on device.
+ if (!Build.IS_USER && value == null) {
+ value = mFlagStore.get(namespace, name);
+ }
+ // If we still don't have a value, maybe DeviceConfig does?
+ // Fallback to sf.getValue() here as well.
+ if (value == null) {
+ value = DeviceConfig.getString(namespace, name, defaultValue);
+ }
+
+ return value;
+ }
+
+ private static class DynamicFlagData {
+ private final String mNamespace;
+ private final String mName;
+ private final Set<Integer> mPids = new HashSet<>();
+ private String mValue;
+ private String mDefaultValue;
+
+ private DynamicFlagData(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ }
+
+ String getValue() {
+ return mValue;
+ }
+
+ void setValue(String value) {
+ mValue = value;
+ }
+
+ String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ void setDefaultValue(String value) {
+ mDefaultValue = value;
+ }
+
+ void addClientPid(int pid) {
+ mPids.add(pid);
+ }
+
+ boolean containsPid(int pid) {
+ return mPids.contains(pid);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof DynamicFlagData)) {
+ return false;
+ }
+
+ DynamicFlagData o = (DynamicFlagData) other;
+
+ return mName.equals(o.mName) && mNamespace.equals(o.mNamespace)
+ && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode() + mNamespace.hashCode()
+ + mValue.hashCode() + mDefaultValue.hashCode();
+ }
+ }
+
+
+ private class BinderGriever implements IBinder.DeathRecipient {
+ private final int mPid;
+
+ private BinderGriever(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(mPid);
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
new file mode 100644
index 000000000000..1fa85325aea6
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
@@ -0,0 +1,168 @@
+/*
+ * 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.flags;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.flags.IFeatureFlags;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.flags.CoreFlags;
+import com.android.server.flags.FeatureFlagsService.PermissionsChecker;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+class FeatureFlagsBinder extends IFeatureFlags.Stub {
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+ private final FlagCache<String> mFlagCache = new FlagCache<>();
+ private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
+ private final PermissionsChecker mPermissionsChecker;
+
+ FeatureFlagsBinder(
+ FlagOverrideStore flagStore,
+ FlagsShellCommand shellCommand,
+ PermissionsChecker permissionsChecker) {
+ mFlagStore = flagStore;
+ mShellCommand = shellCommand;
+ mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
+ mPermissionsChecker = permissionsChecker;
+ }
+
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
+ }
+
+ // Note: The internals of this method should be kept in sync with queryFlags
+ // as they both should return identical results. The difference is that this method
+ // caches any values it receives and/or reads, whereas queryFlags does not.
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
+ int pid = getCallingPid();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+
+ boolean hasFullSyncPrivileges = false;
+ SecurityException permissionFailureException = null;
+ try {
+ assertSyncPermission();
+ hasFullSyncPrivileges = true;
+ } catch (SecurityException e) {
+ permissionFailureException = e;
+ }
+
+ for (SyncableFlag sf : incomingFlags) {
+ if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) {
+ throw permissionFailureException;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ SyncableFlag outFlag;
+ if (sf.isDynamic()) {
+ outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf);
+ } else {
+ synchronized (mFlagCache) {
+ String value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name);
+ value = overrideValue != null ? overrideValue : sf.getValue();
+ mFlagCache.setIfChanged(ns, name, value);
+ }
+ outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false);
+ }
+ }
+ outputFlags.add(outFlag);
+ }
+ return outputFlags;
+ }
+
+ @Override
+ public void overrideFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue());
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.erase(flag.getNamespace(), flag.getName());
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> incomingFlags) {
+ assertSyncPermission();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+ for (SyncableFlag sf : incomingFlags) {
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ String value;
+ String storeValue = mFlagStore.get(ns, name);
+ boolean overridden = storeValue != null;
+
+ if (sf.isDynamic()) {
+ value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue());
+ } else {
+ value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ value = Build.IS_USER ? null : storeValue;
+ if (value == null) {
+ value = sf.getValue();
+ }
+ }
+ }
+ outputFlags.add(new SyncableFlag(
+ sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden));
+ }
+
+ return outputFlags;
+ }
+
+ private void assertSyncPermission() {
+ mPermissionsChecker.assertSyncPermission();
+ clearCallingIdentity();
+ }
+
+ private void assertWritePermission() {
+ mPermissionsChecker.assertWritePermission();
+ clearCallingIdentity();
+ }
+
+
+ @SystemApi
+ public int handleShellCommand(
+ @NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out,
+ @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ FileOutputStream fout = new FileOutputStream(out.getFileDescriptor());
+ FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+
+ return mShellCommand.process(args, fout, ferr);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
new file mode 100644
index 000000000000..93b9e9e0dc8c
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
@@ -0,0 +1,113 @@
+/*
+ * 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.flags;
+
+import static android.Manifest.permission.SYNC_FLAGS;
+import static android.Manifest.permission.WRITE_FLAGS;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.flags.FeatureFlags;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * A service that manages syncing {@link android.flags.FeatureFlags} across processes.
+ *
+ * This service holds flags stable for at least the lifetime of a process, meaning that if
+ * a process comes online with a flag set to true, any other process that connects here and
+ * tries to read the same flag will also receive the flag as true. The flag will remain stable
+ * until either all of the interested processes have died, or the device restarts.
+ *
+ * TODO(279054964): Add to dumpsys
+ * @hide
+ */
+public class FeatureFlagsService extends SystemService {
+
+ static final String TAG = "FeatureFlagsService";
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+
+ /**
+ * Initializes the system service.
+ *
+ * @param context The system server context.
+ */
+ public FeatureFlagsService(Context context) {
+ super(context);
+ mFlagStore = new FlagOverrideStore(
+ new GlobalSettingsProxy(context.getContentResolver()));
+ mShellCommand = new FlagsShellCommand(mFlagStore);
+ }
+
+ @Override
+ public void onStart() {
+ Slog.d(TAG, "Started Feature Flag Service");
+ FeatureFlagsBinder service = new FeatureFlagsBinder(
+ mFlagStore, mShellCommand, new PermissionsChecker(getContext()));
+ publishBinderService(
+ Context.FEATURE_FLAGS_SERVICE, service);
+ publishLocalService(FeatureFlags.class, new FeatureFlags(service));
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Immediately sync our core flags so that they get locked in. We don't want third-party
+ // apps to override them, and syncing immediately is the easiest way to prevent that.
+ FeatureFlags.getInstance().sync();
+ }
+ }
+
+ /**
+ * Delegate for checking flag permissions.
+ */
+ @VisibleForTesting
+ public static class PermissionsChecker {
+ private final Context mContext;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PermissionsChecker(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Ensures that the caller has {@link SYNC_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertSyncPermission() {
+ if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Non-core flag queried. Requires SYNC_FLAGS permission!");
+ }
+ }
+
+ /**
+ * Ensures that the caller has {@link WRITE_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertWritePermission() {
+ if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires WRITE_FLAGS permission!");
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java
new file mode 100644
index 000000000000..cee1578a5dde
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagCache.java
@@ -0,0 +1,103 @@
+/*
+ * 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.flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Threadsafe cache of values that stores the supplied default on cache miss.
+ *
+ * @param <V> The type of value to store.
+ */
+public class FlagCache<V> {
+ private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
+
+ // Cache is organized first by namespace, then by name. All values are stored as strings.
+ final Map<String, Map<String, V>> mCache = new HashMap<>();
+
+ FlagCache() {
+ }
+
+ /**
+ * Returns true if the namespace exists in the cache already.
+ */
+ boolean containsNamespace(String namespace) {
+ synchronized (mCache) {
+ return mCache.containsKey(namespace);
+ }
+ }
+
+ /**
+ * Returns true if the value is stored in the cache.
+ */
+ boolean contains(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ return nsCache != null && nsCache.containsKey(name);
+ }
+ }
+
+ /**
+ * Sets the value if it is different from what is currently stored.
+ *
+ * If the value is not set, or the current value is null, it will store the value and
+ * return true.
+ *
+ * @return True if the value was set. False if the value is the same.
+ */
+ boolean setIfChanged(String namespace, String name, V value) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V curValue = nsCache.get(name);
+ if (curValue == null || !curValue.equals(value)) {
+ nsCache.put(name, value);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, setting it if it is currently absent.
+ *
+ * @return The value that is now in the cache after the call to the method.
+ */
+ V getOrSet(String namespace, String name, V defaultValue) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V value = nsCache.putIfAbsent(name, defaultValue);
+ return value == null ? defaultValue : value;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, returning null if not present.
+ *
+ * @return The value that is now in the cache if there is one.
+ */
+ V getOrNull(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ if (nsCache == null) {
+ return null;
+ }
+ return nsCache.get(name);
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
new file mode 100644
index 000000000000..b1ddc7e67f68
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
@@ -0,0 +1,122 @@
+/*
+ * 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.flags;
+
+import android.database.Cursor;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persistent storage for the {@link FeatureFlagsService}.
+ *
+ * The implementation stores data in Settings.<store> (generally {@link Settings.Global}
+ * is expected).
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagOverrideStore {
+ private static final String KEYNAME_PREFIX = "flag|";
+ private static final String NAMESPACE_NAME_SEPARATOR = ".";
+
+ private final SettingsProxy mSettingsProxy;
+
+ private FlagChangeCallback mCallback;
+
+ FlagOverrideStore(SettingsProxy settingsProxy) {
+ mSettingsProxy = settingsProxy;
+ }
+
+ void setChangeCallback(FlagChangeCallback callback) {
+ mCallback = callback;
+ }
+
+ /** Returns true if a non-null value is in the store. */
+ boolean contains(String namespace, String name) {
+ return get(namespace, name) != null;
+ }
+
+ /** Put a value in the store. */
+ @VisibleForTesting
+ public void set(String namespace, String name, String value) {
+ mSettingsProxy.putString(getPropName(namespace, name), value);
+ mCallback.onFlagChanged(namespace, name, value);
+ }
+
+ /** Read a value out of the store. */
+ @VisibleForTesting
+ public String get(String namespace, String name) {
+ return mSettingsProxy.getString(getPropName(namespace, name));
+ }
+
+ /** Erase a value from the store. */
+ @VisibleForTesting
+ public void erase(String namespace, String name) {
+ set(namespace, name, null);
+ }
+
+ Map<String, Map<String, String>> getFlags() {
+ return getFlagsForNamespace(null);
+ }
+
+ Map<String, Map<String, String>> getFlagsForNamespace(String namespace) {
+ Cursor c = mSettingsProxy.getContentResolver().query(
+ Settings.Global.CONTENT_URI,
+ new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE},
+ null, // Doesn't support a "LIKE" query
+ null,
+ null
+ );
+
+ if (c == null) {
+ return Map.of();
+ }
+ int keynamePrefixLength = KEYNAME_PREFIX.length();
+ Map<String, Map<String, String>> results = new HashMap<>();
+ while (c.moveToNext()) {
+ String key = c.getString(0);
+ if (!key.startsWith(KEYNAME_PREFIX)
+ || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) {
+ continue;
+ }
+ String value = c.getString(1);
+ if (value == null || value.isEmpty()) {
+ continue;
+ }
+ String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR));
+ if (namespace != null && !namespace.equals(ns)) {
+ continue;
+ }
+ String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1);
+ results.putIfAbsent(ns, new HashMap<>());
+ results.get(ns).put(name, value);
+ }
+ c.close();
+ return results;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String getPropName(String namespace, String name) {
+ return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name;
+ }
+
+ interface FlagChangeCallback {
+ void onFlagChanged(String namespace, String name, String value);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
new file mode 100644
index 000000000000..b7896ee18714
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
@@ -0,0 +1,214 @@
+/*
+ * 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.flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Process command line input for the flags service.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagsShellCommand {
+ private final FlagOverrideStore mFlagStore;
+
+ FlagsShellCommand(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ }
+
+ /**
+ * Interpret the command supplied in the constructor.
+ *
+ * @return Zero on success or non-zero on error.
+ */
+ public int process(
+ String[] args,
+ OutputStream out,
+ OutputStream err) {
+ PrintWriter outPw = new FastPrintWriter(out);
+ PrintWriter errPw = new FastPrintWriter(err);
+
+ if (args.length == 0) {
+ return printHelp(outPw);
+ }
+ switch (args[0].toLowerCase(Locale.ROOT)) {
+ case "help":
+ return printHelp(outPw);
+ case "list":
+ return listCmd(args, outPw, errPw);
+ case "set":
+ return setCmd(args, outPw, errPw);
+ case "get":
+ return getCmd(args, outPw, errPw);
+ case "erase":
+ return eraseCmd(args, outPw, errPw);
+ default:
+ return unknownCmd(outPw);
+ }
+ }
+
+ private int printHelp(PrintWriter outPw) {
+ outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of");
+ outPw.println("local flag overrides on a device.");
+ outPw.println();
+ outPw.println("Commands:");
+ outPw.println(" list [namespace]");
+ outPw.println(" List all flag overrides. Namespace is optional.");
+ outPw.println();
+ outPw.println(" get <namespace> <name>");
+ outPw.println(" Return the string value of a specific flag, or <unset>");
+ outPw.println();
+ outPw.println(" set <namespace> <name> <value>");
+ outPw.println(" Set a specific flag");
+ outPw.println();
+ outPw.println(" erase <namespace> <name>");
+ outPw.println(" Unset a specific flag");
+ outPw.flush();
+ return 0;
+ }
+
+ private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 0, 1, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " [namespace]`");
+ errPw.flush();
+ return -1;
+ }
+ Map<String, Map<String, String>> overrides;
+ if (args.length == 2) {
+ overrides = mFlagStore.getFlagsForNamespace(args[1]);
+ } else {
+ overrides = mFlagStore.getFlags();
+ }
+ if (overrides.isEmpty()) {
+ outPw.println("No overrides set");
+ } else {
+ int longestNamespaceLen = "namespace".length();
+ int longestFlagLen = "flag".length();
+ int longestValLen = "value".length();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ longestFlagLen = Math.max(longestFlagLen, flag.getKey().length());
+ longestValLen = Math.max(longestValLen, flag.getValue().length());
+ }
+ }
+ outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace"));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", "flag"));
+ outPw.print(' ');
+ outPw.println("value");
+ for (int i = 0; i < longestNamespaceLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestFlagLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestValLen; i++) {
+ outPw.print('=');
+ }
+ outPw.println();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ outPw.print(
+ String.format("%-" + longestNamespaceLen + "s", namespace.getKey()));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey()));
+ outPw.print(' ');
+ outPw.println(flag.getValue());
+ }
+ }
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 3, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name> <value>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.set(args[1], args[2], args[3]);
+ outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]);
+ outPw.flush();
+ return 0;
+ }
+
+ private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+
+ String value = mFlagStore.get(args[1], args[2]);
+ outPw.print(args[1] + "." + args[2] + " is ");
+ if (value == null || value.isEmpty()) {
+ outPw.println("<unset>");
+ } else {
+ outPw.println("\"" + value.translateEscapes() + "\"");
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.erase(args[1], args[2]);
+ outPw.println("Erased " + args[1] + "." + args[2]);
+ return 0;
+ }
+
+ private int unknownCmd(PrintWriter outPw) {
+ outPw.println("This command is unknown.");
+ printHelp(outPw);
+ outPw.flush();
+ return -1;
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int exactly, String cmdName, PrintWriter errPw) {
+ return validateNumArguments(args, exactly, exactly, cmdName, errPw);
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int min, int max, String cmdName, PrintWriter errPw) {
+ int len = args.length - 1; // Discount the command itself.
+ if (len < min) {
+ errPw.println(
+ "Less than " + min + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ } else if (len > max) {
+ errPw.println(
+ "More than " + max + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
new file mode 100644
index 000000000000..acb7bb5a49db
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
@@ -0,0 +1,68 @@
+/*
+ * 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.flags;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+class GlobalSettingsProxy implements SettingsProxy {
+ private final ContentResolver mContentResolver;
+
+ GlobalSettingsProxy(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ public Uri getUriFor(String name) {
+ return Settings.Global.getUriFor(name);
+ }
+
+ @Override
+ public String getStringForUser(String name, int userHandle) {
+ return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+ }
+
+ @Override
+ public boolean putString(String name, String value, boolean overrideableByRestore) {
+ throw new UnsupportedOperationException(
+ "This method only exists publicly for Settings.System and Settings.Secure");
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, int userHandle) {
+ return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
+ int userHandle, boolean overrideableByRestore) {
+ return Settings.Global.putStringForUser(
+ mContentResolver, name, value, tag, makeDefault, userHandle,
+ overrideableByRestore);
+ }
+
+ @Override
+ public boolean putString(String name, String value, String tag, boolean makeDefault) {
+ return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java
new file mode 100644
index 000000000000..c6e85d5d1dc8
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/SettingsProxy.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Wrapper class meant to enable hermetic testing of {@link Settings}.
+ *
+ * Implementations of this class are expected to be constructed with a {@link ContentResolver} or,
+ * otherwise have access to an implicit one. All the proxy methods in this class exclude
+ * {@link ContentResolver} from their signature and rely on an internally defined one instead.
+ *
+ * Most methods in the {@link Settings} classes have default implementations defined.
+ * Implementations of this interfac need only concern themselves with getting and putting Strings.
+ * They should also override any methods for a class they are proxying that _are not_ defined, and
+ * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global}
+ * does not define {@link #putString(String, String, boolean)}, so an implementation of this
+ * interface that proxies through to it should throw an exception when that method is called.
+ *
+ * This class adds in the following helpers as well:
+ * - {@link #getBool(String)}
+ * - {@link #putBool(String, boolean)}
+ * - {@link #registerContentObserver(Uri, ContentObserver)}
+ *
+ * ... and similar variations for all of those.
+ */
+public interface SettingsProxy {
+
+ /**
+ * Returns the {@link ContentResolver} this instance uses.
+ */
+ ContentResolver getContentResolver();
+
+ /**
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
+ */
+ Uri getUriFor(String name);
+
+ /**See {@link Settings.Secure#getString(ContentResolver, String)} */
+ String getStringForUser(String name, int userHandle);
+
+ /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */
+ boolean putString(String name, String value, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+ boolean putStringForUser(String name, String value, int userHandle);
+
+ /**
+ * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean,
+ * int, boolean)}
+ */
+ boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */
+ boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault);
+
+ /**
+ * Returns the user id for the associated {@link ContentResolver}.
+ */
+ default int getUserId() {
+ return getContentResolver().getUserId();
+ }
+
+ /** See {@link Settings.Secure#getString(ContentResolver, String)} */
+ default String getString(String name) {
+ return getStringForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */
+ default boolean putString(String name, String value) {
+ return putStringForUser(name, value, getUserId());
+ }
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+ default int getIntForUser(String name, int def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /** See {@link Settings.Secure#getInt(ContentResolver, String)} */
+ default int getInt(String name) throws Settings.SettingNotFoundException {
+ return getIntForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */
+ default int getIntForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */
+ default boolean putInt(String name, int value) {
+ return putIntForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+ default boolean putIntForUser(String name, int value, int userHandle) {
+ return putStringForUser(name, Integer.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you. The default value will be returned if the setting is
+ * not defined or not a boolean.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid boolean.
+ */
+ default boolean getBool(String name, boolean def) {
+ return getBoolForUser(name, def, getUserId());
+ }
+
+ /** See {@link #getBool(String, boolean)}. */
+ default boolean getBoolForUser(String name, boolean def, int userHandle) {
+ return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link Settings.SettingNotFoundException}.
+ *
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a boolean.
+ *
+ * @return The setting's current value.
+ */
+ default boolean getBool(String name) throws Settings.SettingNotFoundException {
+ return getBoolForUser(name, getUserId());
+ }
+
+ /** See {@link #getBool(String)}. */
+ default boolean getBoolForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ return getIntForUser(name, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * boolean. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ default boolean putBool(String name, boolean value) {
+ return putBoolForUser(name, value, getUserId());
+ }
+
+ /** See {@link #putBool(String, boolean)}. */
+ default boolean putBoolForUser(String name, boolean value, int userHandle) {
+ return putIntForUser(name, value ? 1 : 0, userHandle);
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String, long)} */
+ default long getLong(String name, long def) {
+ return getLongForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)} */
+ default long getLongForUser(String name, long def, int userHandle) {
+ String valString = getStringForUser(name, userHandle);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String)} */
+ default long getLong(String name) throws Settings.SettingNotFoundException {
+ return getLongForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)} */
+ default long getLongForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String valString = getStringForUser(name, userHandle);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */
+ default boolean putLong(String name, long value) {
+ return putLongForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)} */
+ default boolean putLongForUser(String name, long value, int userHandle) {
+ return putStringForUser(name, Long.toString(value), userHandle);
+ }
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */
+ default float getFloat(String name, float def) {
+ return getFloatForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, float def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String)} */
+ default float getFloat(String name) throws Settings.SettingNotFoundException {
+ return getFloatForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ if (v == null) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */
+ default boolean putFloat(String name, float value) {
+ return putFloatForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */
+ default boolean putFloatForUser(String name, float value, int userHandle) {
+ return putStringForUser(name, Float.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ uri, false, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
+ default void unregisterContentObserver(ContentObserver settingsObserver) {
+ getContentResolver().unregisterContentObserver(settingsObserver);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6960da74dd1a..5f45485d36f4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@ import com.android.server.display.DisplayManagerService;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.flags.FeatureFlagsService;
import com.android.server.gpu.GpuService;
import com.android.server.grammaticalinflection.GrammaticalInflectionService;
import com.android.server.graphics.fonts.FontManagerService;
@@ -1115,6 +1116,12 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
t.traceEnd();
+ // Starts a service for reading runtime flag overrides, and keeping processes
+ // in sync with one another.
+ t.traceBegin("StartFeatureFlagsService");
+ mSystemServiceManager.startService(FeatureFlagsService.class);
+ t.traceEnd();
+
// Uri Grants Manager.
t.traceBegin("UriGrantsManagerService");
mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/OWNERS b/services/tests/displayservicetests/OWNERS
index 6ce1ee4d3de2..6ce1ee4d3de2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/OWNERS
+++ b/services/tests/displayservicetests/OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 3e695c9407b6..0fe6e64b3b54 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -65,6 +65,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -100,6 +101,7 @@ public class LogicalDisplayMapperTest {
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
+ @Mock FoldSettingWrapper mFoldSettingWrapperMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
@@ -139,6 +141,7 @@ public class LogicalDisplayMapperTest {
when(mContextMock.getSystemServiceName(PowerManager.class))
.thenReturn(Context.POWER_SERVICE);
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
when(mContextMock.getResources()).thenReturn(mResourcesMock);
when(mResourcesMock.getBoolean(
@@ -155,7 +158,7 @@ public class LogicalDisplayMapperTest {
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapSpy);
+ mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
}
@@ -571,6 +574,17 @@ public class LogicalDisplayMapperTest {
}
@Test
+ public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+ DEVICE_STATE_OPEN,
+ /* isOverrideActive= */false,
+ /* isInteractive= */true,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldNotBePutToSleep() {
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
@@ -978,4 +992,3 @@ public class LogicalDisplayMapperTest {
}
}
}
-
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index a9e616d766c6..8497dabba67d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,17 +18,23 @@ package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
import android.view.Display;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
@@ -38,6 +44,7 @@ import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrateg
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -64,15 +71,20 @@ public final class DisplayBrightnessStrategySelectorTest {
@Mock
private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
@Mock
- private Context mContext;
- @Mock
private Resources mResources;
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+ private Context mContext;
+
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(contentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
DisplayBrightnessStrategySelector.Injector injector =
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
new file mode 100644
index 000000000000..37d0f6250aaf
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManager;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessThermalClamperTest {
+
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private static final String DISPLAY_ID = "displayId";
+ @Mock
+ private IThermalService mMockThermalService;
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private BrightnessThermalClamper mClamper;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
+ mMockClamperChangeListener, new TestThermalData());
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testTypeIsThermal() {
+ assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Keep
+ private static Object[][] testThrottlingData() {
+ // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
+ return new Object[][] {
+ // no throttling data
+ {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+ // throttlingStatus < min throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+ // throttlingStatus = min throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_MODERATE, true, 0.5f},
+ // throttlingStatus between min and max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_SEVERE, true, 0.5f},
+ // throttlingStatus = max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_CRITICAL, true, 0.1f},
+ // throttlingStatus > max throttling data
+ {List.of(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+ Temperature.THROTTLING_EMERGENCY, true, 0.1f},
+ };
+ }
+ @Test
+ @Parameters(method = "testThrottlingData")
+ public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
+ @Temperature.ThrottlingStatus int throttlingStatus,
+ boolean expectedActive, float expectedBrightness) throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ mTestHandler.flush();
+ assertEquals(expectedActive, mClamper.isActive());
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ @Parameters(method = "testThrottlingData")
+ public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+ @Temperature.ThrottlingStatus int throttlingStatus,
+ boolean expectedActive, float expectedBrightness) throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+ mTestHandler.flush();
+ assertEquals(expectedActive, mClamper.isActive());
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testOverrideData() throws RemoteException {
+ IThermalEventListener thermalEventListener = captureThermalEventListener();
+ thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ mClamper.onDisplayChanged(new TestThermalData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
+ mTestHandler.flush();
+ assertTrue(mClamper.isActive());
+ assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ overrideThrottlingData("displayId,1,emergency,0.4");
+ mClamper.onDeviceConfigChanged();
+ mTestHandler.flush();
+
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ overrideThrottlingData("displayId,1,moderate,0.4");
+ mClamper.onDeviceConfigChanged();
+ mTestHandler.flush();
+
+ assertTrue(mClamper.isActive());
+ assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ private IThermalEventListener captureThermalEventListener() throws RemoteException {
+ ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
+ IThermalEventListener.class);
+ verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
+ Temperature.TYPE_SKIN));
+ return captor.getValue();
+ }
+
+ private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+ return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
+ }
+
+ private void overrideThrottlingData(String data) {
+ mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
+ }
+
+ private class TestInjector extends BrightnessThermalClamper.Injector {
+ @Override
+ IThermalService getThermalService() {
+ return mMockThermalService;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+
+ private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
+
+ private final String mUniqueDisplayId;
+ private final String mDataId;
+ private final ThermalBrightnessThrottlingData mData;
+
+ private TestThermalData() {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ }
+
+ private TestThermalData(List<ThrottlingLevel> data) {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ }
+ private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mDataId = dataId;
+ mData = ThermalBrightnessThrottlingData.create(data);
+ }
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getThermalThrottlingDataId() {
+ return mDataId;
+ }
+
+ @Nullable
+ @Override
+ public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+ return mData;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
new file mode 100644
index 000000000000..5e28e63cfa49
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.PowerManager;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DeviceConfigParsingUtilsTest {
+ private static final String VALID_DATA_STRING = "display1,1,key1,value1";
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private final BiFunction<String, String, Pair<String, String>> mDataPointToPair = Pair::create;
+ private final Function<List<Pair<String, String>>, List<Pair<String, String>>>
+ mDataSetIdentity = (dataSet) -> dataSet;
+
+ @Keep
+ private static Object[][] parseDeviceConfigMapData() {
+ // dataString, expectedMap
+ return new Object[][]{
+ // null
+ {null, Map.of()},
+ // empty string
+ {"", Map.of()},
+ // 1 display, 1 incomplete data point
+ {"display1,1,key1", Map.of()},
+ // 1 display,2 data points required, only 1 present
+ {"display1,2,key1,value1", Map.of()},
+ // 1 display, 1 data point, dataSetId and some extra data
+ {"display1,1,key1,value1,setId1,extraData", Map.of()},
+ // 1 display, random string instead of number of data points
+ {"display1,one,key1,value1", Map.of()},
+ // 1 display, 1 data point no dataSetId
+ {VALID_DATA_STRING, Map.of("display1", Map.of(DisplayDeviceConfig.DEFAULT_ID,
+ List.of(Pair.create("key1", "value1"))))},
+ // 1 display, 1 data point, dataSetId
+ {"display1,1,key1,value1,setId1", Map.of("display1", Map.of("setId1",
+ List.of(Pair.create("key1", "value1"))))},
+ // 1 display, 2 data point, dataSetId
+ {"display1,2,key1,value1,key2,value2,setId1", Map.of("display1", Map.of("setId1",
+ List.of(Pair.create("key1", "value1"), Pair.create("key2", "value2"))))},
+ };
+ }
+
+ @Test
+ @Parameters(method = "parseDeviceConfigMapData")
+ public void testParseDeviceConfigMap(String dataString,
+ Map<String, Map<String, List<Pair<String, String>>>> expectedMap) {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(dataString, mDataPointToPair,
+ mDataSetIdentity);
+
+ assertEquals(expectedMap, result);
+ }
+
+ @Test
+ public void testDataPointMapperReturnsNull() {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, (s1, s2) -> null,
+ mDataSetIdentity);
+
+ assertEquals(Map.of(), result);
+ }
+
+ @Test
+ public void testDataSetMapperReturnsNull() {
+ Map<String, Map<String, List<Pair<String, String>>>> result =
+ DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, mDataPointToPair,
+ (dataSet) -> null);
+
+ assertEquals(Map.of(), result);
+ }
+
+ @Keep
+ private static Object[][] parseThermalStatusData() {
+ // thermalStatusString, expectedThermalStatus
+ return new Object[][]{
+ {"none", PowerManager.THERMAL_STATUS_NONE},
+ {"light", PowerManager.THERMAL_STATUS_LIGHT},
+ {"moderate", PowerManager.THERMAL_STATUS_MODERATE},
+ {"severe", PowerManager.THERMAL_STATUS_SEVERE},
+ {"critical", PowerManager.THERMAL_STATUS_CRITICAL},
+ {"emergency", PowerManager.THERMAL_STATUS_EMERGENCY},
+ {"shutdown", PowerManager.THERMAL_STATUS_SHUTDOWN},
+ };
+ }
+
+ @Test
+ @Parameters(method = "parseThermalStatusData")
+ public void testParseThermalStatus(String thermalStatusString,
+ @PowerManager.ThermalStatus int expectedThermalStatus) {
+ int result = DeviceConfigParsingUtils.parseThermalStatus(thermalStatusString);
+
+ assertEquals(expectedThermalStatus, result);
+ }
+
+ @Test
+ public void testParseThermalStatus_illegalStatus() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseThermalStatus("invalid_status"));
+
+ assertEquals("Invalid Thermal Status: invalid_status", result.getMessage());
+ }
+
+ @Test
+ public void testParseBrightness() {
+ float result = DeviceConfigParsingUtils.parseBrightness("0.65");
+
+ assertEquals(0.65, result, FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testParseBrightness_lessThanMin() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseBrightness("-0.65"));
+
+ assertEquals("Brightness value out of bounds: -0.65", result.getMessage());
+ }
+
+ @Test
+ public void testParseBrightness_moreThanMax() {
+ Throwable result = assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigParsingUtils.parseBrightness("1.65"));
+
+ assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
index 4494b0c412dc..4494b0c412dc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 73eb237fa9e7..410ae35aa790 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -125,6 +125,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
@@ -327,7 +328,7 @@ public class BroadcastQueueTest {
eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
- mConstants.TIMEOUT = 100;
+ mConstants.TIMEOUT = 200;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
@@ -707,6 +708,9 @@ public class BroadcastQueueTest {
private void waitForIdle() throws Exception {
mLooper.release();
mQueue.waitForIdle(LOG_WRITER_INFO);
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandlerThread.getThreadHandler().post(latch::countDown);
+ latch.await();
mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
.acquireLooperManager(mHandlerThread.getLooper()));
}
@@ -2342,6 +2346,7 @@ public class BroadcastQueueTest {
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
@@ -2375,6 +2380,7 @@ public class BroadcastQueueTest {
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1f4563fb2682..9545a8a4544b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -68,7 +68,6 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.answer;
@@ -2522,28 +2521,6 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
- public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() {
- ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
- MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
- doReturn(app).when(sService).getTopApp();
- sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
-
- assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
-
- // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
- // verify that its OOM adjustment level is unaffected.
- bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
- assertFalse(app.mServices.hasAboveClient());
-
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
- assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
- }
-
- @SuppressWarnings("GuardedBy")
- @Test
public void testUpdateOomAdj_DoAll_Side_Cycle() {
final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 50996d7199c8..95c62aeec19a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -43,25 +43,50 @@ public class DisplayBrightnessStateTest {
public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
float brightness = 0.3f;
float sdrBrightness = 0.2f;
+ boolean shouldUseAutoBrightness = true;
BrightnessReason brightnessReason = new BrightnessReason();
brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
- DisplayBrightnessState displayBrightnessState =
- mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
- sdrBrightness).setBrightnessReason(brightnessReason).build();
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+ .setBrightness(brightness)
+ .setSdrBrightness(sdrBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+ .build();
assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+ assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
}
+ @Test
+ public void testFrom() {
+ BrightnessReason reason = new BrightnessReason();
+ reason.setReason(BrightnessReason.REASON_MANUAL);
+ reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+ .setBrightnessReason(reason)
+ .setBrightness(0.26f)
+ .setSdrBrightness(0.23f)
+ .setShouldUseAutoBrightness(false)
+ .build();
+ DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+ assertEquals(state1, state2);
+ }
+
private String getString(DisplayBrightnessState displayBrightnessState) {
StringBuilder sb = new StringBuilder();
- sb.append("DisplayBrightnessState:");
- sb.append("\n brightness:" + displayBrightnessState.getBrightness());
- sb.append("\n sdrBrightness:" + displayBrightnessState.getSdrBrightness());
- sb.append("\n brightnessReason:" + displayBrightnessState.getBrightnessReason());
+ sb.append("DisplayBrightnessState:")
+ .append("\n brightness:")
+ .append(displayBrightnessState.getBrightness())
+ .append("\n sdrBrightness:")
+ .append(displayBrightnessState.getSdrBrightness())
+ .append("\n brightnessReason:")
+ .append(displayBrightnessState.getBrightnessReason())
+ .append("\n shouldUseAutoBrightness:")
+ .append(displayBrightnessState.getShouldUseAutoBrightness());
return sb.toString();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index aaab4033d579..56f650ee9084 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -121,7 +121,8 @@ public final class DisplayPowerController2Test {
private PowerManager mPowerManagerMock;
@Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -714,6 +715,8 @@ public final class DisplayPowerController2Test {
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -751,6 +754,7 @@ public final class DisplayPowerController2Test {
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -1086,6 +1090,18 @@ public final class DisplayPowerController2Test {
.getThermalBrightnessThrottlingDataMapByThrottlingId();
}
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1375,5 +1391,11 @@ public final class DisplayPowerController2Test {
Context context) {
return mHighBrightnessModeController;
}
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 7d26913bd390..e2aeea3bedba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -121,7 +121,8 @@ public final class DisplayPowerControllerTest {
private PowerManager mPowerManagerMock;
@Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@@ -1092,6 +1093,18 @@ public final class DisplayPowerControllerTest {
.getThermalBrightnessThrottlingDataMapByThrottlingId();
}
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1351,5 +1364,11 @@ public final class DisplayPowerControllerTest {
Context context) {
return mHighBrightnessModeController;
}
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ee3a773580a3..0530f892a48e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -34,6 +34,7 @@ android_test {
"services.core",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.net",
"services.people",
"services.usage",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2d4bf144155c..32d0c98d4481 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,8 +44,10 @@ import android.annotation.NonNull;
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.provider.Settings;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -140,8 +142,6 @@ public class FullScreenMagnificationGestureHandlerTest {
@Mock
WindowMagnificationPromptController mWindowMagnificationPromptController;
@Mock
- AccessibilityManagerService mMockAccessibilityManagerService;
- @Mock
AccessibilityTraceManager mMockTraceManager;
@Rule
@@ -153,6 +153,8 @@ public class FullScreenMagnificationGestureHandlerTest {
private long mLastDownTime = Integer.MIN_VALUE;
+ private float mOriginalMagnificationPersistedScale;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -166,6 +168,13 @@ public class FullScreenMagnificationGestureHandlerTest {
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController = new FullScreenMagnificationController(
mockController,
new Object(),
@@ -192,6 +201,10 @@ public class FullScreenMagnificationGestureHandlerTest {
mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mWindowMagnificationPromptController).onDestroy();
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mOriginalMagnificationPersistedScale,
+ UserHandle.USER_SYSTEM);
}
@NonNull
@@ -525,10 +538,9 @@ public class FullScreenMagnificationGestureHandlerTest {
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale + 1.0f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -547,10 +559,9 @@ public class FullScreenMagnificationGestureHandlerTest {
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale - 0.1f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 8346050c3c89..0cfddd30d721 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -106,7 +106,7 @@ public class AuthSessionTest {
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
- @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
+ @Mock private BiometricCameraManager mBiometricCameraManager;
private Random mRandom;
private IBinder mToken;
@@ -609,7 +609,7 @@ public class AuthSessionTest {
TEST_PACKAGE,
checkDevicePolicyManager,
mContext,
- mBiometricSensorPrivacy);
+ mBiometricCameraManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 41f7dbcb0ff5..fc62e75b7d59 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -113,6 +114,10 @@ public class BiometricServiceTest {
private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
private static final String ERROR_USER_CANCELED = "error_user_canceled";
private static final String ERROR_LOCKOUT = "error_lockout";
+ private static final String FACE_SUBTITLE = "face_subtitle";
+ private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+ private static final String DEFAULT_SUBTITLE = "default_subtitle";
+
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
@@ -151,6 +156,8 @@ public class BiometricServiceTest {
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private UserManager mUserManager;
+ @Mock
+ private BiometricCameraManager mBiometricCameraManager;
BiometricContextProvider mBiometricContextProvider;
@@ -177,6 +184,7 @@ public class BiometricServiceTest {
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+ when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -188,6 +196,12 @@ public class BiometricServiceTest {
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+ when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+ .thenReturn(FACE_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+ .thenReturn(FINGERPRINT_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_default_subtitle))
+ .thenReturn(DEFAULT_SUBTITLE);
when(mWindowManager.getDefaultDisplay()).thenReturn(
new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -208,7 +222,7 @@ public class BiometricServiceTest {
@Test
public void testClientBinderDied_whenPaused() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -235,7 +249,7 @@ public class BiometricServiceTest {
@Test
public void testClientBinderDied_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -371,7 +385,7 @@ public class BiometricServiceTest {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -424,9 +438,56 @@ public class BiometricServiceTest {
}
@Test
+ public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FINGERPRINT_SUBTITLE,
+ mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -565,7 +626,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -592,13 +653,13 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
}
@@ -634,7 +695,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
@@ -652,7 +713,7 @@ public class BiometricServiceTest {
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -660,7 +721,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
@@ -688,7 +749,7 @@ public class BiometricServiceTest {
@Test
public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -697,7 +758,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -707,7 +768,7 @@ public class BiometricServiceTest {
@Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -720,7 +781,7 @@ public class BiometricServiceTest {
assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
@@ -756,7 +817,7 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -774,7 +835,7 @@ public class BiometricServiceTest {
// Client receives error immediately
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
// Dialog is hidden immediately
@@ -923,7 +984,7 @@ public class BiometricServiceTest {
int biometricPromptError) throws Exception {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -1120,7 +1181,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1139,7 +1200,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1158,7 +1219,7 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -1172,7 +1233,7 @@ public class BiometricServiceTest {
verify(mBiometricService.mSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1293,7 +1354,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1587,7 +1648,7 @@ public class BiometricServiceTest {
@Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1680,7 +1741,7 @@ public class BiometricServiceTest {
mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1712,7 +1773,7 @@ public class BiometricServiceTest {
strength, mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1795,6 +1856,7 @@ public class BiometricServiceTest {
boolean checkDevicePolicy) {
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(requireConfirmation);
+ promptInfo.setUseDefaultSubtitle(true);
if (authenticators != null) {
promptInfo.setAuthenticators(authenticators);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 0c98c8d88d83..c2bdf501198e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -67,7 +67,7 @@ public class PreAuthInfoTest {
@Mock
BiometricService.SettingObserver mSettingObserver;
@Mock
- BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+ BiometricCameraManager mBiometricCameraManager;
@Before
public void setup() throws RemoteException {
@@ -79,11 +79,13 @@ public class PreAuthInfoTest {
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LOCKOUT_NONE);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
}
@Test
public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@@ -104,15 +106,14 @@ public class PreAuthInfoTest {
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty();
}
@Test
- public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
-
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+ throws Exception {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@Override
@@ -132,8 +133,35 @@ public class PreAuthInfoTest {
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
+
+ @Test
+ public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
new file mode 100644
index 000000000000..df4731fb0bb7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FeatureFlagsServiceTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private FlagOverrideStore mFlagStore;
+ @Mock
+ private FlagsShellCommand mFlagCommand;
+ @Mock
+ private IFeatureFlagsCallback mIFeatureFlagsCallback;
+ @Mock
+ private IBinder mIFeatureFlagsCallbackAsBinder;
+ @Mock
+ private FeatureFlagsService.PermissionsChecker mPermissionsChecker;
+
+ private FeatureFlagsBinder mFeatureFlagsService;
+
+ @Before
+ public void setup() {
+ when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
+ mFeatureFlagsService = new FeatureFlagsBinder(
+ mFlagStore, mFlagCommand, mPermissionsChecker);
+ }
+
+ @Test
+ public void testRegisterCallback() {
+ mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback);
+ try {
+ verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0));
+ } catch (RemoteException e) {
+ fail("Our mock threw a Remote Exception?");
+ }
+ }
+
+ @Test
+ public void testOverrideFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.overrideFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testResetFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.resetFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testSyncFlags_noOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_withSomeOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ assertThat(mFlagStore).isNotNull();
+ when(mFlagStore.get(NS, "c")).thenReturn("true");
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+
+ // Once we've found "c", do an extra check
+ if (outF.getName().equals("c")) {
+ assertWithMessage("Flag " + outF + "was not returned with an override")
+ .that(outF.getValue()).isEqualTo("true");
+ }
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in the first time.
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testQueryFlags_onlyOnce() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.queryFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testQueryFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in (i.e. it wasn't cached).
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testOverrideFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.overrideFlag(f);
+
+ verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue());
+ }
+
+ @Test
+ public void testResetFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.resetFlag(f);
+
+ verify(mFlagStore).erase(f.getNamespace(), f.getName());
+ }
+
+
+ private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) {
+ return a.getNamespace().equals(b.getNamespace())
+ && a.getName().equals(b.getName())
+ && a.isDynamic() == b.isDynamic();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
new file mode 100644
index 000000000000..c2cf540d1d62
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagCacheTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+
+ FlagCache mFlagCache = new FlagCache();
+
+ @Test
+ public void testGetOrNull_unset() {
+ assertThat(mFlagCache.getOrNull(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGetOrSet_unset() {
+ assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value");
+ }
+
+ @Test
+ public void testGetOrSet_alreadySet() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value");
+ }
+
+ @Test
+ public void testSetIfChanged_unset() {
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue();
+ }
+
+ @Test
+ public void testSetIfChanged_noChange() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse();
+ }
+
+ @Test
+ public void testSetIfChanged_changing() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue();
+ }
+
+ @Test
+ public void testContainsNamespace_unset() {
+ assertThat(mFlagCache.containsNamespace(NS)).isFalse();
+ }
+
+ @Test
+ public void testContainsNamespace_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.containsNamespace(NS)).isTrue();
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagCache.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.contains(NS, NAME)).isTrue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
new file mode 100644
index 000000000000..6cc3acfb6125
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FlagOverrideStoreTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private SettingsProxy mSettingsProxy;
+ @Mock
+ private FlagOverrideStore.FlagChangeCallback mCallback;
+
+ private FlagOverrideStore mFlagStore;
+
+ @Before
+ public void setup() {
+ mFlagStore = new FlagOverrideStore(mSettingsProxy);
+ mFlagStore.setChangeCallback(mCallback);
+ }
+
+ @Test
+ public void testSet_unset() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ }
+
+ @Test
+ public void testSet_setTwice() {
+ mFlagStore.set(NS, NAME, "value");
+ mFlagStore.set(NS, NAME, "newvalue");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "newvalue");
+ }
+
+ @Test
+ public void testGet_unset() {
+ assertThat(mFlagStore.get(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGet_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value");
+ }
+
+ @Test
+ public void testErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mSettingsProxy).putString(PROP_NAME, null);
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagStore.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.contains(NS, NAME)).isTrue();
+ }
+
+ @Test
+ public void testCallback_onSet() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mCallback).onFlagChanged(NS, NAME, "value");
+ }
+
+ @Test
+ public void testCallback_onErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mCallback).onFlagChanged(NS, NAME, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS
new file mode 100644
index 000000000000..7ed369e37106
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+include /services/flags/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index c9724a3b4309..a435d605d3f4 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -48,11 +48,11 @@ private fun createKeyboard(
private fun createImeSubtype(
imeSubtypeId: Int,
- languageTag: String,
+ languageTag: ULocale?,
layoutType: String
): InputMethodSubtype =
InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
- .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+ .setPhysicalKeyboardHint(languageTag, layoutType).build()
/**
* Tests for {@link KeyboardMetricsCollector}.
@@ -95,7 +95,8 @@ class KeyboardMetricsCollectorTests {
null,
null
)
- ).addLayoutSelection(createImeSubtype(1, "en-US", "qwerty"), null, 123).build()
+ ).addLayoutSelection(createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+ null, 123).build()
}
}
@@ -111,15 +112,19 @@ class KeyboardMetricsCollectorTests {
)
)
val event = builder.addLayoutSelection(
- createImeSubtype(1, "en-US", "qwerty"),
+ createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
).addLayoutSelection(
- createImeSubtype(2, "en-US", "azerty"),
- null,
+ createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
+ null, // Default layout type
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
).addLayoutSelection(
- createImeSubtype(3, "en-US", "qwerty"),
+ createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
+ KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ ).addLayoutSelection(
+ createImeSubtype(4, null, "qwerty"), // Default language tag
KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
@@ -137,43 +142,62 @@ class KeyboardMetricsCollectorTests {
assertTrue(event.isFirstConfiguration)
assertEquals(
- "KeyboardConfigurationEvent should contain 3 configurations provided",
- 3,
+ "KeyboardConfigurationEvent should contain 4 configurations provided",
+ 4,
event.layoutConfigurations.size
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[0],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "English(US)(Qwerty)",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
- "English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[1],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ KeyboardMetricsCollector.DEFAULT_LAYOUT,
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
- KeyboardMetricsCollector.DEFAULT_LAYOUT,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[2],
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "en-US",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+ )
+ assertExpectedLayoutConfiguration(
+ event.layoutConfigurations[3],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "German",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
}
private fun assertExpectedLayoutConfiguration(
configuration: KeyboardMetricsCollector.LayoutConfiguration,
- expectedLanguageTag: String,
- expectedLayoutType: Int,
+ expectedKeyboardLanguageTag: String,
+ expectedKeyboardLayoutType: Int,
expectedSelectedLayout: String,
- expectedLayoutSelectionCriteria: Int
+ expectedLayoutSelectionCriteria: Int,
+ expectedImeLanguageTag: String,
+ expectedImeLayoutType: Int
) {
- assertEquals(expectedLanguageTag, configuration.keyboardLanguageTag)
- assertEquals(expectedLayoutType, configuration.keyboardLayoutType)
+ assertEquals(expectedKeyboardLanguageTag, configuration.keyboardLanguageTag)
+ assertEquals(expectedKeyboardLayoutType, configuration.keyboardLayoutType)
assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
+ assertEquals(expectedImeLanguageTag, configuration.imeLanguageTag)
+ assertEquals(expectedImeLayoutType, configuration.imeLayoutType)
}
-} \ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 0b13f9a35c1f..5f84e9ee54d0 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -30,6 +30,7 @@ import android.provider.DeviceConfig;
import android.provider.Settings.Global;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.ArrayMap;
import com.android.frameworks.servicestests.R;
@@ -115,6 +116,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
testServiceDefaultValue_On(ServiceType.NULL);
}
+ @Suppress
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
testServiceDefaultValue_Off(ServiceType.VIBRATION);
@@ -200,6 +202,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
testServiceDefaultValue_On(ServiceType.QUICK_DOZE);
}
+ @Suppress
@SmallTest
public void testUpdateConstants_getCorrectData() {
mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7e81ef22eb65..a109d5cddd21 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -73,6 +73,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -405,6 +406,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
UriGrantsManagerInternal mUgmInternal;
@Mock
AppOpsManager mAppOpsManager;
+ private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
@Mock
private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
mNotificationAssistantAccessGrantedCallback;
@@ -604,6 +606,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ doAnswer(invocation -> {
+ mOnPermissionChangeListener = invocation.getArgument(2);
+ return null;
+ }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+ any());
+
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -2002,10 +2010,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
- Thread.sleep(1); // make sure the system clock advances before the next step
+ mTestableLooper.moveTimeForward(1);
// THEN it is canceled
mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
- Thread.sleep(1); // here too
+ mTestableLooper.moveTimeForward(1);
// THEN it is posted again (before the cancel has a chance to finish)
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
@@ -2295,8 +2303,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3034,7 +3042,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0,
- Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
+ Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3061,8 +3069,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3216,48 +3224,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testUpdateAppNotifyCreatorBlock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
- verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -12173,6 +12139,134 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
}
+ @Test
+ public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+ throws RemoteException {
+ // Have preexisting posted notifications from revoked package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from revoked package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+ assertThat(mService.mEnqueuedNotifications).hasSize(1);
+ assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+ "other");
+ }
+
+ @Test
+ public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+ throws RemoteException {
+ // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+ // being now granted, AND having previously posted notifications from said package) should
+ // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+ // scenario, to verify we still handle it reasonably.
+
+ // Have preexisting posted notifications from specific package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from specific package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+ }
+
+ @Test
+ public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse();
+ }
+
+ @Test
+ public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue();
+ }
+
+ @Test
+ public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(1);
+ when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+ true);
+
+ // Start with granted permission and simulate effect of revoking it.
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+ doAnswer(invocation -> {
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+ return null;
+ }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+ mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(0);
+
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+ verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+ }
+
private static <T extends Parcelable> T parcelAndUnparcel(T source,
Parcelable.Creator<T> creator) {
Parcel parcel = Parcel.obtain();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 51a7e747afce..06033c7ebf75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -20,7 +20,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,13 +41,26 @@ import org.junit.Test;
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.function.Supplier;
+/**
+ * Tests for the {@link LetterboxConfigurationPersister} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:LetterboxConfigurationPersisterTest
+ */
@SmallTest
@Presubmit
public class LetterboxConfigurationPersisterTest {
private static final long TIMEOUT = 2000L; // 2 secs
+ private static final int DEFAULT_REACHABILITY_TEST = -1;
+ private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST =
+ () -> DEFAULT_REACHABILITY_TEST;
+
+ private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test";
+
private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
private Context mContext;
private PersisterQueue mPersisterQueue;
@@ -62,7 +74,7 @@ public class LetterboxConfigurationPersisterTest {
mConfigFolder = mContext.getFilesDir();
mPersisterQueue = new PersisterQueue();
mQueueState = new QueueState();
- mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForHorizontalReachability),
() -> mContext.getResources().getInteger(
@@ -72,7 +84,8 @@ public class LetterboxConfigurationPersisterTest {
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForTabletopModeReachability
),
- mConfigFolder, mPersisterQueue, mQueueState);
+ mConfigFolder, mPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
mQueueListener = queueEmpty -> mQueueState.onItemAdded();
mPersisterQueue.addListener(mQueueListener);
mLetterboxConfigurationPersister.start();
@@ -127,8 +140,10 @@ public class LetterboxConfigurationPersisterTest {
public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
final PersisterQueue firstPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- firstPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
firstPersister.start();
firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -138,8 +153,10 @@ public class LetterboxConfigurationPersisterTest {
stopPersisterSafe(firstPersisterQueue);
final PersisterQueue secondPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- secondPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
secondPersister.start();
final int newPositionForHorizontalReachability =
secondPersister.getLetterboxPositionForHorizontalReachability(false);
@@ -156,37 +173,46 @@ public class LetterboxConfigurationPersisterTest {
@Test
public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
- mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
+ firstPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(mPersisterQueue);
final int newPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
+ firstPersister.getLetterboxPositionForHorizontalReachability(false);
final int newPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
+ firstPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
newPositionForVerticalReachability);
- deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
- waitForCompletion(mPersisterQueue);
+ deleteConfiguration(firstPersister, firstPersisterQueue);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ secondPersister.start();
final int positionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
- final int defaultPositionForHorizontalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForHorizontalReachability);
- Assert.assertEquals(defaultPositionForHorizontalReachability,
- positionForHorizontalReachability);
+ secondPersister.getLetterboxPositionForHorizontalReachability(false);
final int positionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
- final int defaultPositionForVerticalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForVerticalReachability);
- Assert.assertEquals(defaultPositionForVerticalReachability,
- positionForVerticalReachability);
+ secondPersister.getLetterboxPositionForVerticalReachability(false);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
}
private void stopPersisterSafe(PersisterQueue persisterQueue) {
@@ -222,7 +248,7 @@ public class LetterboxConfigurationPersisterTest {
private void deleteConfiguration(LetterboxConfigurationPersister persister,
PersisterQueue persisterQueue) {
final AtomicFile fileToDelete = new AtomicFile(
- new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME));
persisterQueue.addItem(
new DeleteFileCommand(fileToDelete, mQueueState.andThen(
s -> persister.useDefaultValue())), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index ed0c8ef489e5..d4fabf40d5d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -61,6 +61,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1425,6 +1426,15 @@ public class TransitionTests extends WindowTestsBase {
// No need to wait for the activity in transient hide task.
assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
+ // An active transient launch overrides idle state to avoid clearing power mode before the
+ // transition is finished.
+ spyOn(mRootWindowContainer.mTransitionController);
+ doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when(
+ mRootWindowContainer.mTransitionController).isTransientLaunch(any());
+ activity2.getTask().setResumedActivity(activity2, "test");
+ activity2.idle = true;
+ assertFalse(mRootWindowContainer.allResumedActivitiesIdle());
+
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3e7919305cbf..3703349b8d7e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9400,6 +9400,39 @@ public class CarrierConfigManager {
"missed_incoming_call_sms_pattern_string_array";
/**
+ * Indicate the satellite services supported per provider by a carrier.
+ *
+ * Key is the PLMN of a satellite provider. Value should be an integer array of supported
+ * services with the following value:
+ * <ul>
+ * <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li>
+ * <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li>
+ * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li>
+ * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li>
+ * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
+ * </ul>
+ * <p>
+ * If this carrier config is not present, the overlay config
+ * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
+ * is present, the supported satellite services will be identified as follows:
+ * <ul>
+ * <li>For the PLMN that exists in both provider supported satellite services and carrier
+ * supported satellite services, the supported services will be the intersection of the two
+ * sets.</li>
+ * <li>For the PLMN that is present in provider supported satellite services but not in carrier
+ * supported satellite services, the provider supported satellite services will be used.</li>
+ * <li>For the PLMN that is present in carrier supported satellite services but not in provider
+ * supported satellite services, the PLMN will be ignored.</li>
+ * </ul>
+ *
+ * This config is empty by default.
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
+ "carrier_supported_satellite_services_per_provider_bundle";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -9621,6 +9654,7 @@ public class CarrierConfigManager {
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -10404,6 +10438,9 @@ public class CarrierConfigManager {
});
sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false);
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
+ sDefaults.putPersistableBundle(
+ KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+ PersistableBundle.EMPTY);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 182d2fcbec67..f012ab56d94d 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -203,6 +203,12 @@ public final class NetworkRegistrationInfo implements Parcelable {
*/
public static final int SERVICE_TYPE_EMERGENCY = 5;
+ /** @hide */
+ public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE;
+
+ /** @hide */
+ public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY;
+
@Domain
private final int mDomain;
@@ -240,7 +246,7 @@ public final class NetworkRegistrationInfo implements Parcelable {
private final boolean mEmergencyOnly;
@ServiceType
- private final ArrayList<Integer> mAvailableServices;
+ private ArrayList<Integer> mAvailableServices;
@Nullable
private CellIdentity mCellIdentity;
@@ -604,6 +610,16 @@ public final class NetworkRegistrationInfo implements Parcelable {
}
/**
+ * Set available service types.
+ *
+ * @param availableServices The list of available services for this network.
+ * @hide
+ */
+ public void setAvailableServices(@NonNull @ServiceType List<Integer> availableServices) {
+ mAvailableServices = new ArrayList<>(availableServices);
+ }
+
+ /**
* @return The access network technology {@link NetworkType}.
*/
public @NetworkType int getAccessNetworkTechnology() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a6099a18fab..340e4ab132ca 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17608,6 +17608,16 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
/**
+ * Purchase premium capability failed because the user disabled the feature.
+ * Subsequent attempts will be throttled for the amount of time specified by
+ * {@link CarrierConfigManager
+ * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+ * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+ * @hide
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17626,7 +17636,8 @@ public class TelephonyManager {
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED})
public @interface PurchasePremiumCapabilityResult {}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
new file mode 100644
index 000000000000..0417f9dbb4bf
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary Activity into Picture-In-Picture mode.
+ *
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
+ *
+ * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest`
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
+ }
+ transitions {
+ testApp.secondaryActivityEnterPip(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Main and secondary activity start from a split each taking half of the screen.
+ */
+ @Presubmit
+ @Test
+ fun layersStartFromEqualSplit() {
+ flicker.assertLayersStart {
+ val leftLayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightLayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ // Compare dimensions of two splits, given we're using default split attributes,
+ // both activities take up the same visible size on the display.
+ check { "height" }
+ .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height)
+ check { "width" }
+ .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width)
+ leftLayerRegion.notOverlaps(rightLayerRegion.region)
+ leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+ }
+ flicker.assertLayersEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Main Activity is visible throughout the transition and becomes fullscreen.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityWindowBecomesFullScreen() {
+ flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+ flicker.assertWmEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Main Activity is visible throughout the transition and becomes fullscreen.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesFullScreen() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(TRANSITION_SNAPSHOT)
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertLayersEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Secondary Activity is visible throughout the transition and shrinks to the bottom right
+ * corner.
+ */
+ @Presubmit
+ @Test
+ fun secondaryWindowShrinks() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ flicker.assertWmEnd {
+ val pipWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check{"height"}
+ .that(pipWindowRegion.region.height)
+ .isLower(startDisplayBounds.height / 2)
+ check{"width"}
+ .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+ }
+ }
+
+ /**
+ * During the transition Secondary Activity shrinks to the bottom right corner.
+ */
+ @Presubmit
+ @Test
+ fun secondaryLayerShrinks() {
+ flicker.assertLayers {
+ val pipLayerList = layers {
+ ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
+ }
+ pipLayerList.zipWithNext { previous, current ->
+ // TODO(b/290987990): Add checks for visibleRegion.
+ current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+ current.screenBounds.notBiggerThan(previous.screenBounds.region)
+ }
+ }
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check { "height" }
+ .that(pipRegion.region.height)
+ .isLower(startDisplayBounds.height / 2)
+ check { "width" }
+ .that(pipRegion.region.width).isLower(startDisplayBounds.width)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index eac88132d410..ade1491fa17b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -113,6 +113,21 @@ constructor(
.waitForAndVerify()
}
+ fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
+ val pipButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
+ FIND_TIMEOUT
+ )
+ require(pipButton != null) { "Can't find enter pip button on screen." }
+ pipButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withPipShown()
+ .waitForAndVerify()
+ }
+
/**
* Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
* a fullscreen window on top of the visible region.
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 68ae806f3c8b..ff9799a1c710 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -102,6 +102,24 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".LaunchTransparentActivity"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:theme="@android:style/Theme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
+ android:label="LaunchTransparentActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".TransparentActivity"
+ android:theme="@style/TransparentTheme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity"
+ android:label="TransparentActivity"
+ android:exported="false">
+ </activity>
<activity android:name=".LaunchNewActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
android:theme="@style/CutoutShortEdges"
@@ -206,6 +224,7 @@
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:supportsPictureInPicture="true"
android:exported="false"/>
<activity
android:name=".ActivityEmbeddingThirdActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
index 67314463161d..135140aa2377 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -35,4 +35,10 @@
android:onClick="launchThirdActivity"
android:text="Launch a third activity" />
+ <Button
+ android:id="@+id/secondary_enter_pip_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="Enter pip" />
+
</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
new file mode 100644
index 000000000000..0730ded66ce4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
new file mode 100644
index 000000000000..ff4ead95f16e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_launch_transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Launch Transparent" />
+ <Button
+ android:id="@+id/button_request_permission"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Request Permission" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 1d21fd56a487..e51ed29adebf 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -43,6 +43,13 @@
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
+ <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index dc21027bc99c..ee087ef9be2c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.content.Intent;
+import android.app.PictureInPictureParams;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
@@ -40,6 +41,16 @@ public class ActivityEmbeddingSecondaryActivity extends Activity {
finish();
}
});
+ findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PictureInPictureParams.Builder picInPicParamsBuilder =
+ new PictureInPictureParams.Builder();
+ enterPictureInPictureMode(picInPicParamsBuilder.build());
+ }
+ }
+ );
}
public void launchThirdActivity(View view) {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 95c86acb9ee9..2795a6c43015 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -73,6 +73,18 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class TransparentActivity {
+ public static final String LABEL = "TransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".TransparentActivity");
+ }
+
+ public static class LaunchTransparentActivity {
+ public static final String LABEL = "LaunchTransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchTransparentActivity");
+ }
+
public static class DialogThemedActivity {
public static final String LABEL = "DialogThemedActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
new file mode 100644
index 000000000000..7c161fd8e611
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent_launch);
+ findViewById(R.id.button_launch_transparent)
+ .setOnClickListener(v -> launchTransparentActivity());
+ }
+
+ private void launchTransparentActivity() {
+ startActivity(new Intent(this, TransparentActivity.class));
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
new file mode 100644
index 000000000000..1bac8bdb003a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent);
+ }
+}
diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS
new file mode 100644
index 000000000000..5a26d0e1f62b
--- /dev/null
+++ b/tests/Internal/src/android/service/wallpaper/OWNERS
@@ -0,0 +1,4 @@
+dupin@google.com
+santie@google.com
+pomini@google.com
+poultney@google.com \ No newline at end of file
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index 153ca79e346b..0c5e8d481131 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -85,4 +85,17 @@ public class WallpaperServiceTest {
assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]);
}
+ @Test
+ public void testNotifyColorsOfDestroyedEngine_doesntCrash() {
+ WallpaperService service = new WallpaperService() {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+ };
+ WallpaperService.Engine engine = service.onCreateEngine();
+ engine.detach();
+
+ engine.notifyColorsChanged();
+ }
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index edd6dd3468ef..82e40b1eee6b 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* This is a wrapper around {@link TestLooperManager} to make it easier to manage
@@ -55,7 +56,6 @@ public class TestableLooper {
private MessageHandler mMessageHandler;
private Handler mHandler;
- private Runnable mEmptyMessage;
private TestLooperManager mQueueWrapper;
static {
@@ -121,8 +121,12 @@ public class TestableLooper {
* @param num Number of messages to parse
*/
public int processMessages(int num) {
+ return processMessagesInternal(num, null);
+ }
+
+ private int processMessagesInternal(int num, Runnable barrierRunnable) {
for (int i = 0; i < num; i++) {
- if (!parseMessageInt()) {
+ if (!processSingleMessage(barrierRunnable)) {
return i + 1;
}
}
@@ -130,6 +134,27 @@ public class TestableLooper {
}
/**
+ * Process up to a certain number of messages, not blocking if the queue has less messages than
+ * that
+ * @param num the maximum number of messages to process
+ * @return the number of messages processed. This will be at most {@code num}.
+ */
+
+ public int processMessagesNonBlocking(int num) {
+ final AtomicBoolean reachedBarrier = new AtomicBoolean(false);
+ Runnable barrierRunnable = () -> {
+ reachedBarrier.set(true);
+ };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ try {
+ return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0);
+ } finally {
+ mHandler.removeCallbacks(barrierRunnable);
+ }
+ }
+
+ /**
* Process messages in the queue until no more are found.
*/
public void processAllMessages() {
@@ -165,19 +190,20 @@ public class TestableLooper {
private int processQueuedMessages() {
int count = 0;
- mEmptyMessage = () -> { };
- mHandler.post(mEmptyMessage);
- waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
- while (parseMessageInt()) count++;
+ Runnable barrierRunnable = () -> { };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ while (processSingleMessage(barrierRunnable)) count++;
return count;
}
- private boolean parseMessageInt() {
+ private boolean processSingleMessage(Runnable barrierRunnable) {
try {
Message result = mQueueWrapper.next();
if (result != null) {
// This is a break message.
- if (result.getCallback() == mEmptyMessage) {
+ if (result.getCallback() == barrierRunnable) {
+ mQueueWrapper.execute(result);
mQueueWrapper.recycle(result);
return false;
}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 0f491b86626c..a02eb6b176dc 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -27,12 +27,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -40,6 +34,11 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -240,4 +239,33 @@ public class TestableLooperTest {
inOrder.verify(handler).dispatchMessage(messageC);
}
+ @Test
+ public void testProcessMessagesNonBlocking_onlyArgNumber() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(2);
+
+ verify(r, times(2)).run();
+ assertEquals(2, processed);
+ }
+
+ @Test
+ public void testProcessMessagesNonBlocking_lessMessagesThanArg() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(5);
+
+ verify(r, times(3)).run();
+ assertEquals(3, processed);
+ }
}