summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp1
-rw-r--r--core/api/current.txt17
-rw-r--r--core/api/module-lib-current.txt2
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt4
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java14
-rw-r--r--core/java/android/app/usage/StorageStats.java75
-rw-r--r--core/java/android/appwidget/flags.aconfig3
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl4
-rw-r--r--core/java/android/content/pm/LauncherApps.java67
-rw-r--r--core/java/android/content/pm/PackageInstaller.java61
-rw-r--r--core/java/android/content/pm/PackageStats.java39
-rw-r--r--core/java/android/content/pm/parsing/ApkLite.java22
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java3
-rw-r--r--core/java/android/credentials/selection/RequestInfo.java35
-rw-r--r--core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java30
-rw-r--r--core/java/android/hardware/input/PhysicalKeyLayout.java15
-rw-r--r--core/java/android/os/ArtModuleServiceManager.java16
-rw-r--r--core/java/android/os/ConfigUpdate.java28
-rw-r--r--core/java/android/os/flags.aconfig8
-rw-r--r--core/java/android/os/storage/IStorageManager.aidl10
-rw-r--r--core/java/android/os/storage/StorageManager.java22
-rw-r--r--core/java/android/service/autofill/AutofillService.java8
-rw-r--r--core/java/android/text/ClientFlags.java7
-rw-r--r--core/java/android/text/MeasuredParagraph.java103
-rw-r--r--core/java/android/text/TextFlags.java2
-rw-r--r--core/java/android/text/flags/flags.aconfig7
-rw-r--r--core/java/android/view/InputDevice.java2
-rw-r--r--core/java/android/view/View.java45
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.java7
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java17
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java17
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java5
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java5
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java5
-rw-r--r--core/java/com/android/server/pm/pkg/AndroidPackage.java7
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp95
-rw-r--r--core/res/res/drawable/ic_notification_summary_auto.xml11
-rw-r--r--core/res/res/values/attrs_manifest.xml4
-rw-r--r--core/res/res/values/config.xml27
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/res/drawable/adaptiveicon_drawable.xml22
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java63
-rw-r--r--core/tests/coretests/src/android/graphics/drawable/IconTest.java107
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java3
-rw-r--r--location/java/android/location/GnssStatus.java2
-rw-r--r--media/java/android/media/audiopolicy/AudioProductStrategy.java12
-rw-r--r--nfc/api/current.txt8
-rw-r--r--nfc/api/system-current.txt2
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java2
-rw-r--r--nfc/java/android/nfc/cardemulation/HostApduService.java20
-rw-r--r--packages/CompanionDeviceManager/Android.bp2
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml2
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml2
-rw-r--r--packages/CredentialManager/res/values/dimens.xml4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt36
-rw-r--r--packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml13
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java309
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java295
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt35
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt28
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt108
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt46
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt62
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt18
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt104
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt317
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt226
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt108
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml22
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml34
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml (renamed from packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml)0
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml8
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml10
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java36
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt936
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt)50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java19
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java15
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java4
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/FillUi.java9
-rw-r--r--services/backup/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/am/PhantomProcessRecord.java7
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java14
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java7
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java77
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java41
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java40
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java28
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettings.java7
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java219
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java121
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java10
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java7
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java148
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java47
-rw-r--r--services/core/java/com/android/server/pm/Settings.java4
-rw-r--r--services/core/java/com/android/server/pm/ShortcutUser.java3
-rw-r--r--services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java7
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java10
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java6
-rw-r--r--services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java6
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java6
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java6
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt12
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java85
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java91
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java381
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java145
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java20
-rw-r--r--services/usage/Android.bp5
-rw-r--r--services/usage/java/com/android/server/usage/StorageStatsService.java31
224 files changed, 5855 insertions, 1755 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index cd991c70d719..9ee74e377f90 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,6 +36,7 @@ aconfig_srcjars = [
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.codec-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
":android.net.platform.flags-aconfig-java{.generated_srcjars}",
diff --git a/core/api/current.txt b/core/api/current.txt
index 283e429c13bf..cb937d200e26 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9357,9 +9357,12 @@ package android.app.usage {
method public long getDataBytes();
method public long getExternalCacheBytes();
method public void writeToParcel(android.os.Parcel, int);
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
- field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3; // 0x3
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2; // 0x2
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0; // 0x0
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4; // 0x4
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1; // 0x1
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 5; // 0x5
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
@@ -12407,7 +12410,7 @@ package android.content.pm {
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
- method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -12421,6 +12424,12 @@ package android.content.pm {
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ @FlaggedApi("android.content.pm.archiving") public static class LauncherApps.ArchiveCompatibilityParams {
+ ctor public LauncherApps.ArchiveCompatibilityParams();
+ method public void setEnableIconOverlay(boolean);
+ method public void setEnableUnarchivalConfirmation(boolean);
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 783bebd23df3..1273da71b748 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -350,7 +350,9 @@ package android.nfc {
package android.os {
public class ArtModuleServiceManager {
+ method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdPreRebootServiceRegisterer();
method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+ method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getDexoptChrootSetupServiceRegisterer();
}
public static final class ArtModuleServiceManager.ServiceRegisterer {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 66e03db5923b..f36d560931da 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3944,7 +3944,9 @@ package android.content.pm {
method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
+ method @FlaggedApi("android.content.pm.set_pre_verified_domains") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public java.util.Set<java.lang.String> getPreVerifiedDomains();
method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
+ method @FlaggedApi("android.content.pm.set_pre_verified_domains") @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public void setPreVerifiedDomains(@NonNull java.util.Set<java.lang.String>);
}
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -4504,6 +4506,7 @@ package android.credentials.selection {
method @NonNull public android.credentials.selection.RequestToken getRequestToken();
method @NonNull public String getType();
method public boolean hasPermissionToOverrideDefault();
+ method public boolean isShowAllOptionsRequested();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
@@ -10389,6 +10392,7 @@ package android.os {
public final class ConfigUpdate {
field public static final String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
field public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
field public static final String ACTION_UPDATE_CONVERSATION_ACTIONS = "android.intent.action.UPDATE_CONVERSATION_ACTIONS";
field public static final String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS";
field public static final String ACTION_UPDATE_EMERGENCY_NUMBER_DB = "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
@@ -10398,6 +10402,7 @@ package android.os {
field public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
field public static final String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION";
field public static final String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
field public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
field public static final String EXTRA_VERSION = "android.os.extra.VERSION";
}
@@ -11168,6 +11173,7 @@ package android.os.storage {
method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
+ method @FlaggedApi("android.os.storage_lifetime_api") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getInternalStorageRemainingLifetime();
method public static boolean hasIsolatedStorage();
method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a21d7c464bf6..4a048bd315a0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1347,8 +1347,8 @@ package android.credentials.selection {
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
- method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
- method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>, boolean);
+ method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean, boolean);
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index ea31ef3ce289..112c5fd808ef 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -26,6 +26,8 @@ import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.server.backup.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
@@ -56,7 +58,7 @@ public final class BackupRestoreEventLogger {
*
* @hide
*/
- public static final int DATA_TYPES_ALLOWED = 15;
+ public static final int DATA_TYPES_ALLOWED = 150;
/**
* Denotes that the annotated element identifies a data type as required by the logging methods
@@ -299,7 +301,7 @@ public final class BackupRestoreEventLogger {
}
if (!mResults.containsKey(dataType)) {
- if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+ if (mResults.keySet().size() == getDataTypesAllowed()) {
// This is a new data type and we're already at capacity.
Slog.d(TAG, "Logger is full, ignoring new data type");
return null;
@@ -315,6 +317,14 @@ public final class BackupRestoreEventLogger {
return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
}
+ private int getDataTypesAllowed(){
+ if (Flags.enableIncreaseDatatypesForAgentLogging()) {
+ return DATA_TYPES_ALLOWED;
+ } else {
+ return 15;
+ }
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 87d97d55318a..7bfaef4e9857 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -40,28 +40,79 @@ public final class StorageStats implements Parcelable {
/** @hide */ public long apkBytes;
/** @hide */ public long libBytes;
/** @hide */ public long dmBytes;
+ /** @hide */ public long dexoptBytes;
+ /** @hide */ public long curProfBytes;
+ /** @hide */ public long refProfBytes;
/** @hide */ public long externalCacheBytes;
- /** Represents all .apk files in application code path.
+ /**
+ * Represents all nonstale dexopt and runtime artifacts of application.
+ * This includes AOT-compiled code and other data that can speed up app execution.
+ * For more detailed information, read the
+ * <a href="https://source.android.com/docs/core/runtime/jit-compiler#flow">JIT compiler</a>
+ * guide.
+ *
+ * Dexopt artifacts become stale when one of their dependencies
+ * has changed. They may be cleaned up or replaced by ART Services at any time.
+ *
+ * For a preload app, this type includes dexopt artifacts on readonly partitions
+ * if they are up-to-date.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type. The sum might include the size of data
+ * that is part of appBytes, dataBytes or cacheBytes.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0;
+
+ /**
+ * Represents reference profile of application.
+ *
+ * Reference profiles are the ones used during the last profile-guided dexopt.
+ * If the last dexopt wasn't profile-guided, then these profiles were not used.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1;
+
+ /**
+ * Represents current profile of application.
+ *
+ * Current profiles may or may not be used during the next profile-guided dexopt.
+ *
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of files of this type. This size fluctuates regularly,
+ * it goes up when the user uses more and more classes/methods and comes down when
+ * a deamon merges this into the ref profile and does profile-guided dexopt.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2;
+
+ /**
+ * Represents all .apk files in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the sum of sizes for files of this type.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+ public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3;
- /** Represents all .dm files in application code path.
+ /**
+ * Represents all .dm files in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the sum of sizes for files of this type.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+ public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4;
- /** Represents lib/ in application code path.
+ /**
+ * Represents lib/ in application code path.
* Can be used as an input to {@link #getAppBytesByDataType(int)}
* to get the size of lib/ directory.
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
- public static final int APP_DATA_TYPE_LIB = 2;
+ public static final int APP_DATA_TYPE_LIB = 5;
/**
* Keep in sync with the file types defined above.
@@ -69,6 +120,9 @@ public final class StorageStats implements Parcelable {
*/
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
@IntDef(flag = false, value = {
+ APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT,
+ APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE,
+ APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE,
APP_DATA_TYPE_FILE_TYPE_APK,
APP_DATA_TYPE_FILE_TYPE_DM,
APP_DATA_TYPE_LIB,
@@ -103,6 +157,9 @@ public final class StorageStats implements Parcelable {
@FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
public long getAppBytesByDataType(@AppDataType int dataType) {
switch (dataType) {
+ case APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT: return dexoptBytes;
+ case APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE: return refProfBytes;
+ case APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE: return curProfBytes;
case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
case APP_DATA_TYPE_LIB: return libBytes;
case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
@@ -161,6 +218,9 @@ public final class StorageStats implements Parcelable {
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheBytes = in.readLong();
+ this.dexoptBytes = in.readLong();
+ this.refProfBytes = in.readLong();
+ this.curProfBytes = in.readLong();
this.apkBytes = in.readLong();
this.libBytes = in.readLong();
this.dmBytes = in.readLong();
@@ -177,6 +237,9 @@ public final class StorageStats implements Parcelable {
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheBytes);
+ dest.writeLong(dexoptBytes);
+ dest.writeLong(refProfBytes);
+ dest.writeLong(curProfBytes);
dest.writeLong(apkBytes);
dest.writeLong(libBytes);
dest.writeLong(dmBytes);
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 084cba37de09..822f02f70562 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -19,6 +19,9 @@ flag {
namespace: "app_widgets"
description: "Move state file IO to non-critical path"
bug: "312949280"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index ea69a2b178dd..9f31aec537ee 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,6 +21,7 @@ import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IOnChecksumsReadyListener;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
+import android.content.pm.verify.domain.DomainSet;
import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
@@ -73,4 +74,7 @@ interface IPackageInstallerSession {
ParcelFileDescriptor getAppMetadataFd();
ParcelFileDescriptor openWriteAppMetadata();
void removeAppMetadata();
+
+ void setPreVerifiedDomains(in DomainSet preVerifiedDomains);
+ DomainSet getPreVerifiedDomains();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983ec938..7c264f65d471 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1802,25 +1802,16 @@ public class LauncherApps {
}
/**
- * Enable or disable different archive compatibility options of the launcher.
- *
- * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
- * that a certain app is archived. True by default.
- * Launchers might want to disable this operation if they want to provide custom user experience
- * to differentiate archived apps.
- * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
- * they click an archived app, which explains that the app will be downloaded and restored in
- * the background. True by default.
- * Launchers might want to disable this operation if they provide sufficient, alternative user
- * guidance to highlight that an unarchival is starting and ongoing once an archived app is
- * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ * Disable different archive compatibility options of the launcher for the caller of this
+ * method.
+ *
+ * @see ArchiveCompatibilityParams for individual options.
*/
@FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
- boolean enableUnarchivalConfirmation) {
+ public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) {
try {
- mService.setArchiveCompatibilityOptions(enableIconOverlay,
- enableUnarchivalConfirmation);
+ mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(),
+ params.isEnableUnarchivalConfirmation());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1982,6 +1973,50 @@ public class LauncherApps {
}
};
+ /**
+ * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public static class ArchiveCompatibilityParams {
+ private boolean mEnableIconOverlay = true;
+
+ private boolean mEnableUnarchivalConfirmation = true;
+
+ /** @hide */
+ public boolean isEnableIconOverlay() {
+ return mEnableIconOverlay;
+ }
+
+ /** @hide */
+ public boolean isEnableUnarchivalConfirmation() {
+ return mEnableUnarchivalConfirmation;
+ }
+
+ /**
+ * If true, provides a cloud overlay for archived apps to ensure users are aware that a
+ * certain app is archived. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they want to provide custom user
+ * experience to differentiate archived apps.
+ */
+ public void setEnableIconOverlay(boolean enableIconOverlay) {
+ this.mEnableIconOverlay = enableIconOverlay;
+ }
+
+ /**
+ * If true, the user is shown a confirmation dialog when they click an archived app, which
+ * explains that the app will be downloaded and restored in the background. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they provide sufficient,
+ * alternative user guidance to highlight that an unarchival is starting and ongoing once an
+ * archived app is tapped. E.g., this could be achieved by showing the unarchival progress
+ * around the icon.
+ */
+ public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) {
+ this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation;
+ }
+ }
+
private static class CallbackMessageHandler extends Handler {
private static final int MSG_ADDED = 1;
private static final int MSG_REMOVED = 2;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5df23c0ff44a..c2ff9f616c22 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -61,6 +61,7 @@ import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
import android.graphics.Bitmap;
import android.icu.util.ULocale;
import android.net.Uri;
@@ -2296,6 +2297,66 @@ public class PackageInstaller {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the pre-verified domains for the app to be installed. By setting pre-verified
+ * domains, the installer allows the app to be opened by the app links of these domains
+ * immediately after it is installed.
+ *
+ * <p>The specified pre-verified domains should be a subset of the hostnames declared with
+ * {@code android:host} and {@code android:autoVerify=true} in the intent filters of the
+ * AndroidManifest.xml of the app. If some of the specified domains are not declared in
+ * the manifest, they will be ignored.</p>
+ * <p>If this API is called multiple times on the same {@link #Session}, the last call
+ * overrides the previous ones.</p>
+ * <p>The instant app installer is the only entity that may call this API.
+ * </p>
+ *
+ * @param preVerifiedDomains domains that are already pre-verified by the installer.
+ *
+ * @throws IllegalArgumentException if the number or the total size of the pre-verified
+ * domains exceeds the maximum allowed, or if the domain
+ * names contain invalid characters.
+ * @throws SecurityException if called from an installer that is not the instant app
+ * installer of the device, or if called after the session has
+ * been committed or abandoned.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ public void setPreVerifiedDomains(@NonNull Set<String> preVerifiedDomains) {
+ Preconditions.checkArgument(preVerifiedDomains != null && !preVerifiedDomains.isEmpty(),
+ "Provided pre-verified domains cannot be null or empty.");
+ try {
+ mSession.setPreVerifiedDomains(new DomainSet(preVerifiedDomains));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the pre-verified domains set in a session.
+ * See {@link #setPreVerifiedDomains(Set)} for the definition of pre-verified domains.
+ *
+ * @throws SecurityException if called from an installer that is not the owner of the
+ * session, or if called after the session has been committed or
+ * abandoned.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+ @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+ @NonNull
+ public Set<String> getPreVerifiedDomains() {
+ try {
+ DomainSet domainSet = mSession.getPreVerifiedDomains();
+ return domainSet != null ? domainSet.getDomains() : Collections.emptySet();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index b919c4b065fd..db3050fc18b6 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -67,6 +67,18 @@ public class PackageStats implements Parcelable {
/** @hide */
public long dmSize;
+ /** Size of dexopt artifacts of the application. */
+ /** @hide */
+ public long dexoptSize;
+
+ /** Size of the current profile of the application. */
+ /** @hide */
+ public long curProfSize;
+
+ /** Size of the reference profile of the application. */
+ /** @hide */
+ public long refProfSize;
+
/**
* Size of the secure container on external storage holding the
* application's code.
@@ -132,6 +144,18 @@ public class PackageStats implements Parcelable {
sb.append(" dm=");
sb.append(dmSize);
}
+ if (dexoptSize != 0) {
+ sb.append(" dexopt=");
+ sb.append(dexoptSize);
+ }
+ if (curProfSize != 0) {
+ sb.append(" curProf=");
+ sb.append(curProfSize);
+ }
+ if (refProfSize != 0) {
+ sb.append(" refProf=");
+ sb.append(refProfSize);
+ }
if (externalCodeSize != 0) {
sb.append(" extCode=");
sb.append(externalCodeSize);
@@ -176,6 +200,9 @@ public class PackageStats implements Parcelable {
apkSize = source.readLong();
libSize = source.readLong();
dmSize = source.readLong();
+ dexoptSize = source.readLong();
+ curProfSize = source.readLong();
+ refProfSize = source.readLong();
externalCodeSize = source.readLong();
externalDataSize = source.readLong();
externalCacheSize = source.readLong();
@@ -192,6 +219,9 @@ public class PackageStats implements Parcelable {
apkSize = pStats.apkSize;
libSize = pStats.libSize;
dmSize = pStats.dmSize;
+ dexoptSize = pStats.dexoptSize;
+ curProfSize = pStats.curProfSize;
+ refProfSize = pStats.refProfSize;
externalCodeSize = pStats.externalCodeSize;
externalDataSize = pStats.externalDataSize;
externalCacheSize = pStats.externalCacheSize;
@@ -212,6 +242,9 @@ public class PackageStats implements Parcelable {
dest.writeLong(apkSize);
dest.writeLong(libSize);
dest.writeLong(dmSize);
+ dest.writeLong(dexoptSize);
+ dest.writeLong(curProfSize);
+ dest.writeLong(refProfSize);
dest.writeLong(externalCodeSize);
dest.writeLong(externalDataSize);
dest.writeLong(externalCacheSize);
@@ -234,6 +267,9 @@ public class PackageStats implements Parcelable {
&& apkSize == otherStats.apkSize
&& libSize == otherStats.libSize
&& dmSize == otherStats.dmSize
+ && dexoptSize == otherStats.dexoptSize
+ && curProfSize == otherStats.curProfSize
+ && refProfSize == otherStats.refProfSize
&& externalCodeSize == otherStats.externalCodeSize
&& externalDataSize == otherStats.externalDataSize
&& externalCacheSize == otherStats.externalCacheSize
@@ -244,7 +280,8 @@ public class PackageStats implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(packageName, userHandle, codeSize, dataSize,
- apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+ apkSize, libSize, dmSize, dexoptSize, curProfSize,
+ refProfSize, cacheSize, externalCodeSize,
externalDataSize, externalCacheSize, externalMediaSize,
externalObbSize);
}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 4990a27c48f8..74ce62c7abff 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -145,6 +145,11 @@ public class ApkLite {
private final boolean mUpdatableSystem;
/**
+ * Name of the emergency installer for the designated system app.
+ */
+ private final @Nullable String mEmergencyInstaller;
+
+ /**
* Archival install info.
*/
private final @Nullable ArchivedPackageParcel mArchivedPackage;
@@ -159,7 +164,8 @@ public class ApkLite {
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+ String emergencyInstaller) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -194,6 +200,7 @@ public class ApkLite {
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
mUpdatableSystem = updatableSystem;
+ mEmergencyInstaller = emergencyInstaller;
mArchivedPackage = null;
}
@@ -232,6 +239,7 @@ public class ApkLite {
mHasDeviceAdminReceiver = false;
mIsSdkLibrary = false;
mUpdatableSystem = true;
+ mEmergencyInstaller = null;
mArchivedPackage = archivedPackage;
}
@@ -550,6 +558,14 @@ public class ApkLite {
}
/**
+ * Name of the emergency installer for the designated system app.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getEmergencyInstaller() {
+ return mEmergencyInstaller;
+ }
+
+ /**
* Archival install info.
*/
@DataClass.Generated.Member
@@ -558,10 +574,10 @@ public class ApkLite {
}
@DataClass.Generated(
- time = 1699587291575L,
+ time = 1706896661616L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 69f9a7db6a85..ffb69c0a2821 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -435,6 +435,7 @@ public class ApkLiteParseUtils {
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isSplitRequired", false);
String configForSplit = parser.getAttributeValue(null, "configForSplit");
+ String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
@@ -644,7 +645,7 @@ public class ApkLiteParseUtils {
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, updatableSystem));
+ hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 2fd322adb79c..60bbae683680 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -110,6 +110,8 @@ public final class RequestInfo implements Parcelable {
private final boolean mHasPermissionToOverrideDefault;
+ private final boolean mIsShowAllOptionsRequested;
+
/**
* Creates new {@code RequestInfo} for a create-credential flow.
*
@@ -121,10 +123,10 @@ public final class RequestInfo implements Parcelable {
public static RequestInfo newCreateRequestInfo(
@NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
@NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
- @NonNull List<String> defaultProviderIds) {
+ @NonNull List<String> defaultProviderIds, boolean isShowAllOptionsRequested) {
return new RequestInfo(
token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
- hasPermissionToOverrideDefault, defaultProviderIds);
+ hasPermissionToOverrideDefault, defaultProviderIds, isShowAllOptionsRequested);
}
/**
@@ -137,11 +139,12 @@ public final class RequestInfo implements Parcelable {
@NonNull
public static RequestInfo newGetRequestInfo(
@NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
- @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+ @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+ boolean isShowAllOptionsRequested) {
return new RequestInfo(
token, TYPE_GET, appPackageName, null, getCredentialRequest,
hasPermissionToOverrideDefault,
- /*defaultProviderIds=*/ new ArrayList<>());
+ /*defaultProviderIds=*/ new ArrayList<>(), isShowAllOptionsRequested);
}
@@ -218,12 +221,31 @@ public final class RequestInfo implements Parcelable {
return mGetCredentialRequest;
}
+ /**
+ * Returns true if all options should be immediately displayed in the UI, and false otherwise.
+ *
+ * Normally this bit is set to false, upon which the selection UI should first display a
+ * condensed view of popular, deduplicated options that is determined based on signals like
+ * last-used timestamps, credential type priorities, and preferred providers configured from the
+ * user settings {@link #getDefaultProviderIds()}; at the same time, the UI should offer an
+ * option (button) that navigates the user to viewing all options from this condensed view.
+ *
+ * In some special occasions, e.g. when a request is initiated from the autofill drop-down
+ * suggestion, this bit will be set to true to indicate that the selection UI should immediately
+ * render the all option UI. This means that the request initiator has collected a user signal
+ * to confirm that the user wants to view all the available options at once.
+ */
+ public boolean isShowAllOptionsRequested() {
+ return mIsShowAllOptionsRequested;
+ }
+
private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
@NonNull String appPackageName,
@Nullable CreateCredentialRequest createCredentialRequest,
@Nullable GetCredentialRequest getCredentialRequest,
boolean hasPermissionToOverrideDefault,
- @NonNull List<String> defaultProviderIds) {
+ @NonNull List<String> defaultProviderIds,
+ boolean isShowAllOptionsRequested) {
mToken = token;
mType = type;
mAppPackageName = appPackageName;
@@ -232,6 +254,7 @@ public final class RequestInfo implements Parcelable {
mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
mRegistryProviderIds = new ArrayList<>();
+ mIsShowAllOptionsRequested = isShowAllOptionsRequested;
}
private RequestInfo(@NonNull Parcel in) {
@@ -254,6 +277,7 @@ public final class RequestInfo implements Parcelable {
mHasPermissionToOverrideDefault = in.readBoolean();
mDefaultProviderIds = in.createStringArrayList();
mRegistryProviderIds = in.createStringArrayList();
+ mIsShowAllOptionsRequested = in.readBoolean();
}
@Override
@@ -266,6 +290,7 @@ public final class RequestInfo implements Parcelable {
dest.writeBoolean(mHasPermissionToOverrideDefault);
dest.writeStringList(mDefaultProviderIds);
dest.writeStringList(mRegistryProviderIds);
+ dest.writeBoolean(mIsShowAllOptionsRequested);
}
@Override
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index d943c37e9e5b..1cc910c87b4f 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -219,26 +219,22 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
if (!glyphData.hasBaseText()) {
return;
}
- if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+ boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+ mBaseTextPaint));
+ if (glyphData.hasValidShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
- } else if (glyphData.hasValidShiftText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
- } else if (glyphData.hasValidAltGrText()) {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+ GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+ mModifierTextPaint));
+ }
+ if (glyphData.hasValidAltGrText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
- } else {
- mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_CENTER, mBaseTextPaint));
+ }
+ if (glyphData.hasValidAltShiftText()) {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
+ GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
}
}
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 3454c399375a..844e02f00777 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -396,6 +396,7 @@ final class PhysicalKeyLayout {
private final String mBaseText;
private final String mShiftText;
private final String mAltGrText;
+ private final String mAltGrShiftText;
public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON);
@@ -403,6 +404,9 @@ final class PhysicalKeyLayout {
KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
mAltGrText = getKeyText(kcm, keyCode,
KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON);
+ mAltGrShiftText = getKeyText(kcm, keyCode,
+ KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_LEFT_ON
+ | KeyEvent.META_SHIFT_ON);
}
public String getBaseText() {
@@ -417,6 +421,10 @@ final class PhysicalKeyLayout {
return mAltGrText;
}
+ public String getAltGrShiftText() {
+ return mAltGrShiftText;
+ }
+
public boolean hasBaseText() {
return !TextUtils.isEmpty(mBaseText);
}
@@ -428,5 +436,12 @@ final class PhysicalKeyLayout {
public boolean hasValidAltGrText() {
return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
}
+
+ public boolean hasValidAltShiftText() {
+ return !TextUtils.isEmpty(mAltGrShiftText)
+ && !TextUtils.equals(mBaseText, mAltGrShiftText)
+ && !TextUtils.equals(mAltGrText, mAltGrShiftText)
+ && !TextUtils.equals(mShiftText, mAltGrShiftText);
+ }
}
}
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index 0009e61f3a6f..e0b631d69ca8 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -15,9 +15,11 @@
*/
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.pm.Flags;
/**
* Provides a way to register and obtain the system service binder objects managed by the ART
@@ -60,4 +62,18 @@ public class ArtModuleServiceManager {
public ServiceRegisterer getArtdServiceRegisterer() {
return new ServiceRegisterer("artd");
}
+
+ /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+ public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
+ return new ServiceRegisterer("artd_pre_reboot");
+ }
+
+ /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+ public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
+ return new ServiceRegisterer("dexopt_chroot_setup");
+ }
}
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index 4908919e59c0..87cd4ee577af 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -131,6 +132,23 @@ public final class ConfigUpdate {
"android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
/**
+ * Broadcast intent action indicating that the updated config data is available.
+ * This broadcast intent action is to be sent by the config updater app, and will be received
+ * and handled by the platform.
+ * <p>Extra: {@link #EXTRA_VERSION} the numeric version of the database.
+ * <p>Extra: {@link #EXTRA_REQUIRED_HASH} hash of the database, which is encoded by base-16
+ * SHA512
+ * <p>Extra: {@link #EXTRA_DOMAIN} the string identifying the affected module
+ * <p>Input: {@link android.content.Intent#getData} the URI to download config data file
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
+
+ /**
* An integer to indicate the numeric version of the new data. Devices should only install
* if the update version is newer than the current one.
*
@@ -147,6 +165,16 @@ public final class ConfigUpdate {
@SystemApi
public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
+ /**
+ * String identifying the affected module.
+ * Devices apply the updated config data to the module specified in the string.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
+
private ConfigUpdate() {
}
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 82518bfbfd8d..6c728a4a7288 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -114,3 +114,11 @@ flag {
is_fixed_read_only: true
bug: "309792384"
}
+
+flag {
+ name: "storage_lifetime_api"
+ namespace: "phoenix"
+ description: "Feature flag for adding storage component health APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 54ed73c34830..1ab48a22cbbd 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -175,4 +175,12 @@ interface IStorageManager {
void setCloudMediaProvider(in String authority) = 96;
String getCloudMediaProvider() = 97;
long getInternalStorageBlockDeviceSize() = 98;
-} \ No newline at end of file
+ /**
+ * Returns the remaining lifetime of the internal storage device, as an
+ * integer percentage. For example, 90 indicates that 90% of the storage
+ * device's useful lifetime remains. If no information is available, -1
+ * is returned.
+ */
+ @EnforcePermission("READ_PRIVILEGED_PHONE_STATE")
+ int getInternalStorageRemainingLifetime() = 99;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3a57e84ee404..5a095410bef6 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,7 @@ import static android.os.UserHandle.PER_USER_RANGE;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,6 +59,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Flags;
import android.os.Handler;
import android.os.IInstalld;
import android.os.IVold;
@@ -2939,4 +2941,24 @@ public class StorageManager {
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int CRYPT_TYPE_DEFAULT = 1;
+
+ /**
+ * Returns the remaining lifetime of the internal storage device, as an integer percentage. For
+ * example, 90 indicates that 90% of the storage device's useful lifetime remains. If no
+ * information is available, -1 is returned.
+ *
+ * @return Percentage of the remaining useful lifetime of the internal storage device.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_STORAGE_LIFETIME_API)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getInternalStorageRemainingLifetime() {
+ try {
+ return mStorageManager.getInternalStorageRemainingLifetime();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 298bdb881e9f..e6a84df16c27 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -600,6 +600,14 @@ public abstract class AutofillService extends Service {
*/
public static final String EXTRA_ERROR = "error";
+ /**
+ * Name of the key used to mark whether the fill response is for a webview.
+ *
+ * @hide
+ */
+ public static final String WEBVIEW_REQUESTED_CREDENTIAL_KEY = "webview_requested_credential";
+
+
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 0421d5aaa69b..32f05be5a83a 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -54,4 +54,11 @@ public class ClientFlags {
public static boolean fixLineHeightForLocale() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
}
+
+ /**
+ * @see Flags#icuBidiMigration()
+ */
+ public static boolean icuBidiMigration() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_ICU_BIDI_MIGRATION);
+ }
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index b268c2edd9a7..09f15c3aca3e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -30,6 +30,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
+import android.icu.text.Bidi;
import android.text.AutoGrowArray.ByteArray;
import android.text.AutoGrowArray.FloatArray;
import android.text.AutoGrowArray.IntArray;
@@ -115,6 +116,11 @@ public class MeasuredParagraph {
// This is empty if mLtrWithoutBidi is true.
private @NonNull ByteArray mLevels = new ByteArray();
+ // The bidi level for runs.
+ private @NonNull ByteArray mRunLevels = new ByteArray();
+
+ private Bidi mBidi;
+
// The whole width of the text.
// See getWholeWidth comments.
private @FloatRange(from = 0.0f) float mWholeWidth;
@@ -148,6 +154,7 @@ public class MeasuredParagraph {
reset();
mLevels.clearWithReleasingLargeArray();
mWidths.clearWithReleasingLargeArray();
+ mRunLevels.clearWithReleasingLargeArray();
mFontMetrics.clearWithReleasingLargeArray();
mSpanEndCache.clearWithReleasingLargeArray();
}
@@ -160,10 +167,12 @@ public class MeasuredParagraph {
mCopiedBuffer = null;
mWholeWidth = 0;
mLevels.clear();
+ mRunLevels.clear();
mWidths.clear();
mFontMetrics.clear();
mSpanEndCache.clear();
mMeasuredText = null;
+ mBidi = null;
}
/**
@@ -193,6 +202,13 @@ public class MeasuredParagraph {
* @hide
*/
public @Layout.Direction int getParagraphDir() {
+ if (ClientFlags.icuBidiMigration()) {
+ if (mBidi == null) {
+ return Layout.DIR_LEFT_TO_RIGHT;
+ }
+ return (mBidi.getParaLevel() & 0x01) == 0
+ ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+ }
return mParaDir;
}
@@ -204,6 +220,62 @@ public class MeasuredParagraph {
*/
public Directions getDirections(@IntRange(from = 0) int start, // inclusive
@IntRange(from = 0) int end) { // exclusive
+ if (ClientFlags.icuBidiMigration()) {
+ // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
+ if (mBidi == null) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ // Easy case: If the original text only contains single directionality run, the
+ // substring is only single run.
+ if (start == end) {
+ if ((mBidi.getParaLevel() & 0x01) == 0) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ }
+ }
+
+ // Okay, now we need to generate the line instance.
+ Bidi bidi = mBidi.createLineBidi(start, end);
+
+ // Easy case: If the line instance only contains single directionality run, no need
+ // to reorder visually.
+ if (bidi.getRunCount() == 1) {
+ if ((bidi.getParaLevel() & 0x01) == 1) {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ } else {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+ }
+
+ // Reorder directionality run visually.
+ mRunLevels.resize(bidi.getRunCount());
+ byte[] levels = mRunLevels.getRawArray();
+ for (int i = 0; i < bidi.getRunCount(); ++i) {
+ levels[i] = (byte) bidi.getRunLevel(i);
+ }
+ int[] visualOrders = Bidi.reorderVisual(levels);
+
+ int[] dirs = new int[bidi.getRunCount() * 2];
+ for (int i = 0; i < bidi.getRunCount(); ++i) {
+ int vIndex;
+ if ((mBidi.getBaseLevel() & 0x01) == 1) {
+ // For the historical reasons, if the base directionality is RTL, the Android
+ // draws from the right, i.e. the visually reordered run needs to be reversed.
+ vIndex = visualOrders[bidi.getRunCount() - i - 1];
+ } else {
+ vIndex = visualOrders[i];
+ }
+
+ // Special packing of dire
+ dirs[i * 2] = bidi.getRunStart(vIndex);
+ dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
+ | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
+ }
+
+ return new Directions(dirs);
+ }
if (mLtrWithoutBidi) {
return Layout.DIRS_ALL_LEFT_TO_RIGHT;
}
@@ -608,6 +680,37 @@ public class MeasuredParagraph {
}
}
+ if (ClientFlags.icuBidiMigration()) {
+ if ((textDir == TextDirectionHeuristics.LTR
+ || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+ || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+ && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+ mLevels.clear();
+ mLtrWithoutBidi = true;
+ return;
+ }
+ final int bidiRequest;
+ if (textDir == TextDirectionHeuristics.LTR) {
+ bidiRequest = Bidi.LTR;
+ } else if (textDir == TextDirectionHeuristics.RTL) {
+ bidiRequest = Bidi.RTL;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+ bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+ bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
+ } else {
+ final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+ bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
+ }
+ mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+ mLevels.resize(mTextLength);
+ byte[] rawArray = mLevels.getRawArray();
+ for (int i = 0; i < mTextLength; ++i) {
+ rawArray[i] = mBidi.getLevelAt(i);
+ }
+ mLtrWithoutBidi = false;
+ return;
+ }
if ((textDir == TextDirectionHeuristics.LTR
|| textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
|| textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 24663862400d..770e5c97f50a 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -59,6 +59,7 @@ public final class TextFlags {
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
+ Flags.FLAG_ICU_BIDI_MIGRATION,
};
/**
@@ -71,6 +72,7 @@ public final class TextFlags {
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
Flags.fixLineHeightForLocale(),
+ Flags.icuBidiMigration(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f3e0ea780182..a49aee1d023d 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -103,3 +103,10 @@ flag {
description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
bug: "300850862"
}
+
+flag {
+ name: "icu_bidi_migration"
+ namespace: "text"
+ description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
+ bug: "317144801"
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 891e2a2d4b20..d22d2a52c8cc 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1544,7 +1544,7 @@ public final class InputDevice implements Parcelable {
* source, this method returns {@code false}.
*
* @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
- * @param source the {link InputDevice} source from which the {@link MotionEvent} that
+ * @param source the {@link InputDevice} source from which the {@link MotionEvent} that
* triggers the scroll came.
* @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
* if smooth scrolling is not necessary, or if the provided axis and source combination
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2366ff77692b..5c5817feb23b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15868,20 +15868,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if (onFilterTouchEventForSecurity(event)) {
- if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
- result = true;
- }
- //noinspection SimplifiableIfStatement
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null
- && (mViewFlags & ENABLED_MASK) == ENABLED
- && li.mOnTouchListener.onTouch(this, event)) {
- result = true;
- }
-
- if (!result && onTouchEvent(event)) {
- result = true;
- }
+ result = performOnTouchCallback(event);
}
if (!result && mInputEventConsistencyVerifier != null) {
@@ -15900,6 +15887,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return result;
}
+ /**
+ * Returns {@code true} if the {@link MotionEvent} from {@link #dispatchTouchEvent} was
+ * handled by this view.
+ */
+ private boolean performOnTouchCallback(MotionEvent event) {
+ boolean handled = false;
+ if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+ handled = true;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
+ handled = li.mOnTouchListener.onTouch(this, event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ if (handled) {
+ return true;
+ }
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View#onTouchEvent");
+ return onTouchEvent(event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
boolean isAccessibilityFocusedViewOrHost() {
return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
.getAccessibilityFocusedHost() == this);
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 9ef68807419a..0cc9a0d75154 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -50,6 +50,8 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
static final int RESULT_CODE_UNREGISTER = 1;
@NonNull
private final ResultReceiver mResultReceiver;
+ @NonNull
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
public ImeOnBackInvokedDispatcher(Handler handler) {
mResultReceiver = new ResultReceiver(handler) {
@@ -88,7 +90,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
// cause a memory leak because the app side already clears the reference correctly.
final IOnBackInvokedCallback iCallback =
new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(
- callback, false /* useWeakRef */);
+ callback, mProgressAnimator, false /* useWeakRef */);
bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
bundle.putInt(RESULT_KEY_PRIORITY, priority);
bundle.putInt(RESULT_KEY_ID, callback.hashCode());
@@ -179,6 +181,9 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
}
}
mImeCallbacks.clear();
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
}
static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index baefe7be1f54..5c911f4a632a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -246,7 +246,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback, this);
+ : new OnBackInvokedCallbackWrapper(
+ callback,
+ mProgressAnimator,
+ this);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -272,7 +275,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
@NonNull
- private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
private boolean mIsDispatching = false;
/**
@@ -339,18 +342,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
* forwarded and registered on the app's {@link WindowOnBackInvokedDispatcher}. */
@Nullable
private final WindowOnBackInvokedDispatcher mDispatcher;
+ @NonNull
+ private final BackProgressAnimator mProgressAnimator;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
+ @NonNull BackProgressAnimator progressAnimator,
WindowOnBackInvokedDispatcher dispatcher) {
mCallbackRef = new CallbackRef(callback, true /* useWeakRef */);
+ mProgressAnimator = progressAnimator;
mDispatcher = dispatcher;
}
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
+ @NonNull BackProgressAnimator progressAnimator,
boolean useWeakRef) {
mCallbackRef = new CallbackRef(callback, useWeakRef);
+ mProgressAnimator = progressAnimator;
mDispatcher = null;
}
@@ -362,11 +371,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- mProgressAnimator.onBackStarted(backEvent, event ->
- callback.onBackProgressed(event));
callback.onBackStarted(new BackEvent(
backEvent.getTouchX(), backEvent.getTouchY(),
backEvent.getProgress(), backEvent.getSwipeEdge()));
+ mProgressAnimator.onBackStarted(backEvent, event ->
+ callback.onBackProgressed(event));
}
});
}
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 83acc47d637f..d433ca3246b6 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -227,6 +227,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
private String requiredAccountType;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
+ private String mEmergencyInstaller;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
private String overlayTarget;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
@@ -1275,6 +1278,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return restrictedAccountType;
}
+ @Nullable
+ @Override
+ public String getEmergencyInstaller() {
+ return mEmergencyInstaller;
+ }
+
@Override
public int getRoundIconResourceId() {
return roundIconRes;
@@ -2336,6 +2345,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
}
@Override
+ public PackageImpl setEmergencyInstaller(@Nullable String emergencyInstaller) {
+ this.mEmergencyInstaller = emergencyInstaller;
+ return this;
+ }
+
+ @Override
public PackageImpl setRoundIconResourceId(int value) {
roundIconRes = value;
return this;
@@ -3105,6 +3120,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
dest.writeString(this.mBaseApkPath);
dest.writeString(this.restrictedAccountType);
dest.writeString(this.requiredAccountType);
+ dest.writeString(this.mEmergencyInstaller);
sForInternedString.parcel(this.overlayTarget, dest, flags);
dest.writeString(this.overlayTargetOverlayableName);
dest.writeString(this.overlayCategory);
@@ -3255,6 +3271,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
this.mBaseApkPath = in.readString();
this.restrictedAccountType = in.readString();
this.requiredAccountType = in.readString();
+ this.mEmergencyInstaller = in.readString();
this.overlayTarget = sForInternedString.unparcel(in);
this.overlayTargetOverlayableName = in.readString();
this.overlayCategory = in.readString();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 7ef0b4864c84..66cfb69d7871 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -75,6 +75,11 @@ public interface ParsedPackage extends AndroidPackage {
ParsedPackage setUpdatableSystem(boolean value);
+ /**
+ * Sets a system app that is allowed to update another system app
+ */
+ ParsedPackage setEmergencyInstaller(String emergencyInstaller);
+
ParsedPackage markNotActivitiesAsNotExportedIfSingleUser();
ParsedPackage setOdm(boolean odm);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 6c09b7c04fa7..ef106e0268a6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -347,6 +347,11 @@ public interface ParsingPackage {
ParsingPackage setUpdatableSystem(boolean value);
+ /**
+ * Sets a system app that is allowed to update another system app
+ */
+ ParsingPackage setEmergencyInstaller(String emergencyInstaller);
+
ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index f48359759e21..e0fdbc68a41f 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -952,6 +952,8 @@ public class ParsingPackageUtils {
final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
"updatableSystem", true);
+ final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,
+ "emergencyInstaller");
pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
R.styleable.AndroidManifest_installLocation, sa))
@@ -959,7 +961,8 @@ public class ParsingPackageUtils {
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
- .setUpdatableSystem(updatableSystem);
+ .setUpdatableSystem(updatableSystem)
+ .setEmergencyInstaller(emergencyInstaller);
boolean foundApp = false;
final int depth = parser.getDepth();
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index adb0c6959f12..096f246b1bab 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -273,6 +273,13 @@ public interface AndroidPackage {
String getRestrictedAccountType();
/**
+ * @see R.styleable#AndroidManifestApplication_emergencyInstaller
+ * @hide
+ */
+ @Nullable
+ String getEmergencyInstaller();
+
+ /**
* @see ApplicationInfo#roundIconRes
* @see R.styleable#AndroidManifestApplication_roundIcon
*/
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e325a5c1338..58166bf513b9 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -667,8 +667,9 @@ static void EnableKeepCapabilities(fail_fn_t fail_fn) {
}
}
-static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn, jlong bounding_capabilities) {
for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if ((1LL << i) & bounding_capabilities) continue;
if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
@@ -680,6 +681,27 @@ static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
}
}
+static bool MatchGid(JNIEnv* env, jintArray gids, jint gid, jint gid_to_find) {
+ if (gid == gid_to_find) return true;
+
+ if (gids == nullptr) return false;
+
+ jsize gids_num = env->GetArrayLength(gids);
+ ScopedIntArrayRO native_gid_proxy(env, gids);
+
+ if (native_gid_proxy.get() == nullptr) {
+ RuntimeAbort(env, __LINE__, "Bad gids array");
+ }
+
+ for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
+ if (native_gid_proxy[gids_index] == gid_to_find) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
@@ -1875,9 +1897,9 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list,
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jlong permitted_capabilities,
- jlong effective_capabilities, jint mount_external,
- jstring managed_se_info, jstring managed_nice_name,
- bool is_system_server, bool is_child_zygote,
+ jlong effective_capabilities, jlong bounding_capabilities,
+ jint mount_external, jstring managed_se_info,
+ jstring managed_nice_name, bool is_system_server, bool is_child_zygote,
jstring managed_instruction_set, jstring managed_app_data_dir,
bool is_top_app, jobjectArray pkg_data_info_list,
jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
@@ -1891,6 +1913,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
auto instruction_set = extract_fn(managed_instruction_set);
auto app_data_dir = extract_fn(managed_app_data_dir);
+ // Permit bounding capabilities
+ permitted_capabilities |= bounding_capabilities;
+
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
EnableKeepCapabilities(fail_fn);
@@ -1898,7 +1923,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
SetInheritable(permitted_capabilities, fail_fn);
- DropCapabilitiesBoundingSet(fail_fn);
+ DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);
bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
android::NativeBridgeAvailable() &&
@@ -2165,6 +2190,23 @@ static uint64_t GetEffectiveCapabilityMask(JNIEnv* env) {
return capdata[0].effective | (static_cast<uint64_t>(capdata[1].effective) << 32);
}
+static jlong CalculateBoundingCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids) {
+ jlong capabilities = 0;
+
+ /*
+ * Grant CAP_SYS_NICE to CapInh/CapPrm/CapBnd for processes that can spawn
+ * VMs. This enables processes to execve on binaries with elevated
+ * capabilities if its file capability bits are set. This does not grant
+ * capability to the parent process(that spawns the VM) as the effective
+ * bits are not set.
+ */
+ if (MatchGid(env, gids, gid, AID_VIRTUALMACHINE)) {
+ capabilities |= (1LL << CAP_SYS_NICE);
+ }
+
+ return capabilities;
+}
+
static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids,
bool is_child_zygote) {
jlong capabilities = 0;
@@ -2198,26 +2240,7 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi
* Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
*/
- bool gid_wakelock_found = false;
- if (gid == AID_WAKELOCK) {
- gid_wakelock_found = true;
- } else if (gids != nullptr) {
- jsize gids_num = env->GetArrayLength(gids);
- ScopedIntArrayRO native_gid_proxy(env, gids);
-
- if (native_gid_proxy.get() == nullptr) {
- RuntimeAbort(env, __LINE__, "Bad gids array");
- }
-
- for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
- if (native_gid_proxy[gids_index] == AID_WAKELOCK) {
- gid_wakelock_found = true;
- break;
- }
- }
- }
-
- if (gid_wakelock_found) {
+ if (MatchGid(env, gids, gid, AID_WAKELOCK)) {
capabilities |= (1LL << CAP_BLOCK_SUSPEND);
}
@@ -2494,6 +2517,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
zygote::ZygoteFailure(env, "zygote", nice_name,
@@ -2532,10 +2556,11 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
return pid;
}
@@ -2568,7 +2593,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
// System server prcoess does not need data isolation so no need to
// know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
- effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+ effective_capabilities, 0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
/* pkg_data_info_list */ nullptr,
/* allowlisted_data_info_list */ nullptr, false, false, false);
@@ -2725,12 +2750,14 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
/**
diff --git a/core/res/res/drawable/ic_notification_summary_auto.xml b/core/res/res/drawable/ic_notification_summary_auto.xml
new file mode 100644
index 000000000000..908bba889068
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_summary_auto.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M260,760Q236,760 218,742Q200,724 200,700L200,140Q200,116 218,98Q236,80 260,80L820,80Q844,80 862,98Q880,116 880,140L880,700Q880,724 862,742Q844,760 820,760L260,760ZM260,700L820,700Q820,700 820,700Q820,700 820,700L820,140Q820,140 820,140Q820,140 820,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700ZM140,880Q116,880 98,862Q80,844 80,820L80,200L140,200L140,820Q140,820 140,820Q140,820 140,820L760,820L760,880L140,880ZM260,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700L260,700Q260,700 260,700Q260,700 260,700L260,140Q260,140 260,140Q260,140 260,140Z"/>
+</vector>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 65c4d9fdab78..d91094042402 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1608,6 +1608,10 @@
This is a private attribute, used without android: namespace. -->
<attr name="updatableSystem" format="boolean" />
+ <!-- Allows each installer in the system image to designate another app in the system image to
+ update the installer. -->
+ <attr name="emergencyInstaller" format="string" />
+
<!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags
together. -->
<attr name="foregroundServiceType">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 951625ea844d..d4e727ea0afb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5385,19 +5385,20 @@
and a second time clipped to the fill level to indicate charge -->
<bool name="config_batterymeterDualTone">false</bool>
- <!-- The default refresh rate for a given device. This value is used to set the
- global refresh rate vote, and when set to zero it has no effect on the vote.
- If this value is non-zero but the hardware composer on the device supports
- display modes with higher refresh rates, the framework may use those higher
- refresh rate modes if an app chooses one by setting preferredDisplayModeId
- or calling setFrameRate().-->
- <integer name="config_defaultRefreshRate">0</integer>
-
- <!-- The default peak refresh rate for a given device. This value is used to set the
- global peak refresh rate vote, and when set to zero it has no effect on the vote.
- Change this value to non-zero if you want to prevent the framework from using higher
- refresh rates, even if display modes with higher refresh rates are available from
- hardware composer. -->
+ <!-- The default refresh rate for a given device. Change this value to set a higher default
+ refresh rate. If the hardware composer on the device supports display modes with a higher
+ refresh rate than the default value specified here, the framework may use those higher
+ refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+ setFrameRate().
+ If a non-zero value is set for config_defaultPeakRefreshRate, then
+ config_defaultRefreshRate may be set to 0, in which case the value set for
+ config_defaultPeakRefreshRate will act as the default frame rate. -->
+ <integer name="config_defaultRefreshRate">60</integer>
+
+ <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+ the framework from using higher refresh rates, even if display modes with higher refresh
+ rates are available from hardware composer. Only has an effect if the value is
+ non-zero. -->
<integer name="config_defaultPeakRefreshRate">0</integer>
<!-- External display peak refresh rate for the given device. Change this value if you want to
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b8a399b9f77b..4cf37df185bf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3146,6 +3146,7 @@
<java-symbol type="drawable" name="ic_collapse_notification" />
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
+ <java-symbol type="drawable" name="ic_notification_summary_auto" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
<java-symbol type="dimen" name="notification_header_shrink_hide_width" />
<java-symbol type="dimen" name="notification_content_margin_start" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1b25d7fa32a4..ee1a4acb3839 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@ android_test {
"android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
+ "androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
"androidx.test.runner",
@@ -90,6 +91,7 @@ android_test {
"flickerlib-parsers",
"flickerlib-trace_processor_shell",
"mockito-target-extended-minus-junit4",
+ "TestParameterInjector",
],
libs: [
diff --git a/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
new file mode 100644
index 000000000000..dcffe75cbcfd
--- /dev/null
+++ b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground android:drawable="@android:color/black"/>
+ <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 6e1c5803bbd0..0aefef2cd2d3 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -26,10 +26,14 @@ import static junit.framework.Assert.fail;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,7 +47,9 @@ import java.util.Optional;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupRestoreEventLoggerTest {
- private static final int DATA_TYPES_ALLOWED = 15;
+ private static final int DATA_TYPES_ALLOWED_AFTER_FLAG = 150;
+
+ private static final int DATA_TYPES_ALLOWED_BEFORE_FLAG = 15;
private static final String DATA_TYPE_1 = "data_type_1";
private static final String DATA_TYPE_2 = "data_type_2";
@@ -55,6 +61,9 @@ public class BackupRestoreEventLoggerTest {
private BackupRestoreEventLogger mLogger;
private MessageDigest mHashDigest;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -83,10 +92,53 @@ public class BackupRestoreEventLoggerTest {
}
@Test
- public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ public void testBackupLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ mLogger.logItemsBackedUp(dataType, /* count */ 5);
+ mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logBackupMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
+ }
+
+ mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testRestoreLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ mLogger.logItemsRestored(dataType, /* count */ 5);
+ mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
+ }
+
+ mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testBackupLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
mLogger = new BackupRestoreEventLogger(BACKUP);
- for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
String dataType = DATA_TYPE_1 + i;
mLogger.logItemsBackedUp(dataType, /* count */ 5);
mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
@@ -103,10 +155,11 @@ public class BackupRestoreEventLoggerTest {
}
@Test
- public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ public void testRestoreLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
mLogger = new BackupRestoreEventLogger(RESTORE);
- for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
String dataType = DATA_TYPE_1 + i;
mLogger.logItemsRestored(dataType, /* count */ 5);
mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 49ed3a83e3d4..950925f488fd 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,8 +18,13 @@ package android.graphics.drawable;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.app.IUriGrantsManager;
import android.content.ContentProvider;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
@@ -32,13 +37,15 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
-import android.test.AndroidTestCase;
import android.util.Log;
-import androidx.test.filters.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
import com.android.frameworks.coretests.R;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -46,13 +53,29 @@ import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
-public class IconTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class IconTest {
public static final String TAG = IconTest.class.getSimpleName();
+ private Context mContext;
+
public static void L(String s, Object... parts) {
Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
}
- @SmallTest
+ private Context getContext() {
+ return mContext;
+ }
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
public void testWithBitmap() throws Exception {
final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -119,7 +142,7 @@ public class IconTest extends AndroidTestCase {
}
}
- @SmallTest
+ @Test
public void testScaleDownIfNecessary() throws Exception {
final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
final Icon ic = Icon.createWithBitmap(bm);
@@ -132,7 +155,7 @@ public class IconTest extends AndroidTestCase {
assertThat(ic.getBitmap().getHeight()).isLessThan(21);
}
- @SmallTest
+ @Test
public void testWithAdaptiveBitmap() throws Exception {
final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
@@ -166,7 +189,7 @@ public class IconTest extends AndroidTestCase {
}
}
- @SmallTest
+ @Test
public void testWithBitmapResource() throws Exception {
final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -193,7 +216,7 @@ public class IconTest extends AndroidTestCase {
* Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
* stat_sys_adb is assumed, and asserted, to be a vector drawable.)
*/
- @SmallTest
+ @Test
public void testWithStatSysAdbResource() throws Exception {
// establish reference bitmap
final float dp = getContext().getResources().getDisplayMetrics().density;
@@ -244,7 +267,7 @@ public class IconTest extends AndroidTestCase {
}
}
- @SmallTest
+ @Test
public void testWithFile() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -268,7 +291,55 @@ public class IconTest extends AndroidTestCase {
}
}
- @SmallTest
+ @Test
+ public void testWithAdaptiveIconResource_useMonochrome() throws Exception {
+ final int colorMono = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.system_accent2_800)).getColor();
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, true, 0.0f);
+ final Drawable draw1 = im1.loadDrawable(mContext);
+ assertThat(draw1 instanceof InsetDrawable).isTrue();
+ ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable();
+ assertThat(colorDrawable.getColor()).isEqualTo(colorMono);
+ }
+
+ @Test
+ public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception {
+ final int colorMono = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.system_accent2_800)).getColor();
+ final int colorFg = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.black)).getColor();
+ final int colorBg = ((ColorDrawable) getContext().getDrawable(
+ android.R.color.white)).getColor();
+
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, false , 0.0f);
+ final Drawable draw1 = im1.loadDrawable(mContext);
+ assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue();
+ ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getMonochrome();
+ assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono);
+ ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getForeground();
+ assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg);
+ ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+ .getBackground();
+ assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg);
+ }
+
+ @Test
+ public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome)
+ throws Exception {
+ final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+ R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f);
+ final Parcel parcel = Parcel.obtain();
+ im1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final Icon im2 = Icon.CREATOR.createFromParcel(parcel);
+ assertThat(im1.sameAs(im2)).isTrue();
+ }
+
+ @Test
public void testAsync() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -311,7 +382,7 @@ public class IconTest extends AndroidTestCase {
L(TAG, "asyncTest: done");
}
- @SmallTest
+ @Test
public void testParcel() throws Exception {
final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
.getBitmap();
@@ -391,7 +462,7 @@ public class IconTest extends AndroidTestCase {
return (int) Math.sqrt(maxNumPixels / aspRatio);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithBitmap() throws Exception {
final int bmpWidth = 13_000;
final int bmpHeight = 10_000;
@@ -408,7 +479,7 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
final int bmpWidth = 20_000;
final int bmpHeight = 10_000;
@@ -427,7 +498,7 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithResource() throws Exception {
final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
@@ -435,7 +506,7 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithFile() throws Exception {
final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
.getBitmap();
@@ -450,7 +521,7 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testScaleDownMaxSizeWithData() throws Exception {
final int bmpBpp = 4;
final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
@@ -465,7 +536,7 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
- @SmallTest
+ @Test
public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
int uid = 12345;
String packageName = "test_pkg";
@@ -509,7 +580,7 @@ public class IconTest extends AndroidTestCase {
}
}
- @SmallTest
+ @Test
public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
int uid = 12345;
String packageName = "test_pkg";
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 91e620cd4b83..0baaff0bb2fc 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@ applications that come with the platform
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.UPDATE_CONFIG"/>
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 45e29a88c7db..f359025f4b46 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -154,6 +154,12 @@ public final class Icon implements Parcelable {
// TYPE_DATA: data offset
private int mInt2;
+ // TYPE_RESOURCE: use the monochrome drawable from an AdaptiveIconDrawable
+ private boolean mUseMonochrome = false;
+
+ // TYPE_RESOURCE: wrap the monochrome drawable in an InsetDrawable with the specified inset
+ private float mInsetScale = 0.0f;
+
/**
* Gets the type of the icon provided.
* <p>
@@ -368,10 +374,34 @@ public final class Icon implements Parcelable {
result.setTintList(mTintList);
result.setTintBlendMode(mBlendMode);
}
+
+ if (mUseMonochrome) {
+ return crateMonochromeDrawable(result, mInsetScale);
+ }
+
return result;
}
/**
+ * Gets the monochrome drawable from an {@link AdaptiveIconDrawable}.
+ *
+ * @param drawable An {@link AdaptiveIconDrawable}
+ * @return Adjusted (wrapped in {@link InsetDrawable}) monochrome drawable
+ * from an {@link AdaptiveIconDrawable}.
+ * Or the original drawable if no monochrome layer exists.
+ */
+ private static Drawable crateMonochromeDrawable(Drawable drawable, float inset) {
+ if (drawable instanceof AdaptiveIconDrawable) {
+ Drawable monochromeDrawable = ((AdaptiveIconDrawable) drawable).getMonochrome();
+ // wrap with negative inset => scale icon (inspired from BaseIconFactory)
+ if (monochromeDrawable != null) {
+ return new InsetDrawable(monochromeDrawable, inset);
+ }
+ }
+ return drawable;
+ }
+
+ /**
* Resizes image if size too large for Canvas to draw
* @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
* @return resized bitmap
@@ -693,7 +723,9 @@ public final class Icon implements Parcelable {
&& Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
case TYPE_RESOURCE:
return getResId() == otherIcon.getResId()
- && Objects.equals(getResPackage(), otherIcon.getResPackage());
+ && Objects.equals(getResPackage(), otherIcon.getResPackage())
+ && mUseMonochrome == otherIcon.mUseMonochrome
+ && mInsetScale == otherIcon.mInsetScale;
case TYPE_URI:
case TYPE_URI_ADAPTIVE_BITMAP:
return Objects.equals(getUriString(), otherIcon.getUriString());
@@ -748,6 +780,26 @@ public final class Icon implements Parcelable {
}
/**
+ * Create an Icon pointing to a drawable resource.
+ * @param resPackage Name of the package containing the resource in question
+ * @param resId ID of the drawable resource
+ * @param useMonochrome if this icon should use the monochrome res from the adaptive drawable
+ * @hide
+ */
+ public static @NonNull Icon createWithResourceAdaptiveDrawable(@NonNull String resPackage,
+ @DrawableRes int resId, boolean useMonochrome, float inset) {
+ if (resPackage == null) {
+ throw new IllegalArgumentException("Resource package name must not be null.");
+ }
+ final Icon rep = new Icon(TYPE_RESOURCE);
+ rep.mInt1 = resId;
+ rep.mUseMonochrome = useMonochrome;
+ rep.mInsetScale = inset;
+ rep.mString1 = resPackage;
+ return rep;
+ }
+
+ /**
* Create an Icon pointing to a bitmap in memory.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
@@ -986,6 +1038,8 @@ public final class Icon implements Parcelable {
final int resId = in.readInt();
mString1 = pkg;
mInt1 = resId;
+ mUseMonochrome = in.readBoolean();
+ mInsetScale = in.readFloat();
break;
case TYPE_DATA:
final int len = in.readInt();
@@ -1027,6 +1081,8 @@ public final class Icon implements Parcelable {
case TYPE_RESOURCE:
dest.writeString(getResPackage());
dest.writeInt(getResId());
+ dest.writeBoolean(mUseMonochrome);
+ dest.writeFloat(mInsetScale);
break;
case TYPE_DATA:
dest.writeInt(getDataLength());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 621c45378cbe..0aa89598cd10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -919,6 +919,9 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
+ } else if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
}
}
@@ -1403,6 +1406,13 @@ public class BubbleController implements ConfigurationChangeListener,
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
+
+ if (!mBubbleData.hasBubbles()) {
+ // if there are no bubbles, don't create the stack or layer views. they will be created
+ // later when the first bubble is added.
+ return;
+ }
+
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
@@ -1732,6 +1742,10 @@ public class BubbleController implements ConfigurationChangeListener,
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
mLayerView.removeBubble(removedBubble);
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b95d258da6fd..62f2726ad9bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -273,6 +273,9 @@ public class BubbleBarLayerView extends FrameLayout
if (endAction != null) {
endAction.run();
}
+ if (mBubbleData.getBubbles().isEmpty()) {
+ mBubbleController.onAllBubblesAnimatedOut();
+ }
};
if (mDragController != null && mDragController.isStuckToDismiss()) {
mAnimationHelper.animateDismiss(runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 52a06e0bbe7f..9f73f1bad092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1891,6 +1891,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removeContentOverlay: %s, state=%s, surface=%s",
+ mTaskInfo, mPipTransitionState, surface);
if (mPipOverlay != null) {
if (mPipOverlay != surface) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 09f40e80f885..970bfdaa5465 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -195,7 +195,7 @@ public final class GnssStatus implements Parcelable {
* <li>SBAS: 120-151, 183-192</li>
* <li>GLONASS: One of: OSN, or FCN+100
* <ul>
- * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li>
+ * <li>1-25 as the orbital slot number (OSN) (preferred, if known)</li>
* <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
* i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
* </ul></li>
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e8adfaf4fc34..48ca4bff00c9 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -50,6 +50,14 @@ public final class AudioProductStrategy implements Parcelable {
private static final String TAG = "AudioProductStrategy";
+ /**
+ * The audio flags that will affect product strategy selection.
+ */
+ private static final int AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION =
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED
+ | AudioAttributes.FLAG_SCO
+ | AudioAttributes.FLAG_BEACON;
+
private final AudioAttributesGroup[] mAudioAttributesGroups;
private final String mName;
/**
@@ -438,8 +446,8 @@ public final class AudioProductStrategy implements Parcelable {
|| (attr.getSystemUsage() == refAttr.getSystemUsage()))
&& ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
|| (attr.getContentType() == refAttr.getContentType()))
- && ((refAttr.getAllFlags() == 0)
- || (attr.getAllFlags() != 0
+ && (((refAttr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) == 0)
+ || ((attr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) != 0
&& (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
&& ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 28cf250c40be..845a8f97db10 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -232,13 +232,13 @@ package android.nfc.cardemulation {
method public final void sendResponseApdu(byte[]);
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 768013644b12..dd2e17409368 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -23,7 +23,7 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
- method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
+ method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 782af5f3fe19..252f46fc40a4 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -3103,7 +3103,7 @@ public final class NfcAdapter {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
mNfcVendorNciCallbackListener.unregister(callback);
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 7cd2533a7dbf..89b03226ed46 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,11 +244,11 @@ public abstract class HostApduService extends Service {
public static final String KEY_DATA = "data";
/**
- * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+ * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
* polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
/**
* POLLING_LOOP_TYPE_A is the value associated with the key
@@ -299,33 +299,33 @@ public abstract class HostApduService extends Service {
public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
/**
- * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
- * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+ * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
- * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+ * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
/**
* @hide
*/
- public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+ public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
"android.nfc.cardemulation.POLLING_FRAMES";
/**
@@ -405,7 +405,7 @@ public abstract class HostApduService extends Service {
break;
case MSG_POLLING_LOOP:
ArrayList<Bundle> frames =
- msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+ msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
Bundle.class);
processPollingFrames(frames);
break;
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index f6458c26f309..ce32ec4321dc 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -46,4 +46,6 @@ android_app {
],
platform_apis: true,
+
+ generate_product_characteristics_rro: true,
}
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index fdda9ea06ab9..910ff962a231 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index c7c2fda6a489..4bf0e990126e 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 53852cbd0d10..b47a4dc2b76f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -26,6 +26,6 @@
<dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
- <integer name="autofill_max_visible_datasets">3</integer>
- <dimen name="dropdown_touch_target_min_width">48dp</dimen>
+ <integer name="autofill_max_visible_datasets">5</integer>
+ <dimen name="dropdown_touch_target_min_height">48dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 30681f39d929..0ccb07a6d475 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -108,7 +108,8 @@ class CredentialManagerRepo(
isReqForAllOptions = intent.getBooleanExtra(
Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
/*defaultValue=*/ false
- )
+ ) || (requestInfo?.isShowAllOptionsRequested ?: false) // TODO(b/323552850) - Remove
+ // usage on Constants.EXTRA_REQ_FOR_ALL_OPTIONS once it is deprecated.
val cancellationRequest = getCancelUiRequest(intent)
val cancelUiRequestState = cancellationRequest?.let {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 07f1fa34580b..2628f0932797 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -116,8 +116,10 @@ class CredentialAutofillService : AutofillService() {
return
}
+ val responseClientState = Bundle()
+ responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId, responseClientState)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -153,7 +155,8 @@ class CredentialAutofillService : AutofillService() {
return
}
- val fillResponse = convertToFillResponse(result, request)
+ val fillResponse = convertToFillResponse(result, request,
+ responseClientState)
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -260,7 +263,8 @@ class CredentialAutofillService : AutofillService() {
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest
+ filLRequest: FillRequest,
+ responseClientState: Bundle
): FillResponse? {
val candidateProviders = getCredResponse.candidateProviderDataList
if (candidateProviders.isEmpty()) {
@@ -281,6 +285,7 @@ class CredentialAutofillService : AutofillService() {
if (!validFillResponse) {
return null
}
+ fillResponseBuilder.setClientState(responseClientState)
return fillResponseBuilder.build()
}
@@ -313,7 +318,7 @@ class CredentialAutofillService : AutofillService() {
maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
- (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+ (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount - 1)
var i = 0
var datasetAdded = false
@@ -578,10 +583,11 @@ class CredentialAutofillService : AutofillService() {
private fun getCredManRequest(
structure: AssistStructure,
sessionId: Int,
- requestId: Int
+ requestId: Int,
+ responseClientState: Bundle
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- traverseStructure(structure, credentialOptions)
+ traverseStructure(structure, credentialOptions, responseClientState)
if (credentialOptions.isNotEmpty()) {
val dataBundle = Bundle()
@@ -596,7 +602,8 @@ class CredentialAutofillService : AutofillService() {
private fun traverseStructure(
structure: AssistStructure,
- cmRequests: MutableList<CredentialOption>
+ cmRequests: MutableList<CredentialOption>,
+ responseClientState: Bundle
) {
val windowNodes: List<AssistStructure.WindowNode> =
structure.run {
@@ -604,16 +611,17 @@ class CredentialAutofillService : AutofillService() {
}
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- traverseNode(windowNode.rootViewNode, cmRequests)
+ traverseNode(windowNode.rootViewNode, cmRequests, responseClientState)
}
}
private fun traverseNode(
viewNode: AssistStructure.ViewNode,
- cmRequests: MutableList<CredentialOption>
+ cmRequests: MutableList<CredentialOption>,
+ responseClientState: Bundle
) {
viewNode.autofillId?.let {
- val options = getCredentialOptionsFromViewNode(viewNode, it)
+ val options = getCredentialOptionsFromViewNode(viewNode, it, responseClientState)
cmRequests.addAll(options)
}
@@ -623,13 +631,14 @@ class CredentialAutofillService : AutofillService() {
}
children.forEach { childNode: AssistStructure.ViewNode ->
- traverseNode(childNode, cmRequests)
+ traverseNode(childNode, cmRequests, responseClientState)
}
}
private fun getCredentialOptionsFromViewNode(
viewNode: AssistStructure.ViewNode,
- autofillId: AutofillId
+ autofillId: AutofillId,
+ responseClientState: Bundle
): List<CredentialOption> {
// TODO(b/293945193) Replace with isCredential check from viewNode
val credentialHints: MutableList<String> = mutableListOf()
@@ -637,6 +646,9 @@ class CredentialAutofillService : AutofillService() {
for (hint in viewNode.autofillHints!!) {
if (hint.startsWith(CRED_HINT_PREFIX)) {
credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
+ if (viewNode.webDomain != null) {
+ responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+ }
}
}
}
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 2ded3c6e82eb..89d6ac39598b 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -38,6 +38,17 @@
android:src="@drawable/add_a_photo_circled"
android:layout_gravity="bottom|right"/>
</FrameLayout>
+ <TextView
+ android:id="@+id/edit_user_info_message"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_marginTop="24dp"
+ android:textAppearance="@style/android:TextAppearance.Material.Body1"
+ android:text="@string/edit_user_info_message"
+ />
<EditText
android:id="@+id/user_name"
@@ -46,6 +57,8 @@
android:layout_gravity="center"
android:minWidth="200dp"
android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_marginTop="24dp"
android:minHeight="@dimen/min_tap_target_size"
android:ellipsize="end"
android:singleLine="true"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e64212d298a..1092a16216f9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1484,6 +1484,9 @@
<!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
<string name="user_nickname">Nickname</string>
+ <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
+ <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+
<!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
<string name="user_add_user">Add user</string>
<!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fb14a172d76c..58e0a89d5387 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -41,6 +41,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
@@ -66,18 +67,29 @@ import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.settingslib.utils.BuildCompatUtils;
import java.text.NumberFormat;
+import java.time.Duration;
import java.util.List;
public class Utils {
private static final String TAG = "Utils";
- @VisibleForTesting
- static final String STORAGE_MANAGER_ENABLED_PROPERTY =
- "ro.storage_manager.enabled";
-
public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
"incompatible_charger_warning_disabled";
+ public static final String WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP =
+ "wireless_charging_notification_timestamp";
+
+ @VisibleForTesting
+ static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
+
+ @VisibleForTesting static final long WIRELESS_CHARGING_DEFAULT_TIMESTAMP = -1L;
+
+ @VisibleForTesting
+ static final long WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS =
+ Duration.ofDays(30).toMillis();
+
+ @VisibleForTesting
+ static final String WIRELESS_CHARGING_WARNING_ENABLED = "wireless_charging_warning_enabled";
private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
@@ -101,19 +113,19 @@ public class Utils {
R.drawable.ic_show_x_wifi_signal_4
};
- public static void updateLocationEnabled(Context context, boolean enabled, int userId,
- int source) {
+ /** Update the location enable state. */
+ public static void updateLocationEnabled(
+ @NonNull Context context, boolean enabled, int userId, int source) {
Settings.Secure.putIntForUser(
- context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
- userId);
+ context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source, userId);
LocationManager locationManager = context.getSystemService(LocationManager.class);
locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
/**
- * Return string resource that best describes combination of tethering
- * options available on this device.
+ * Return string resource that best describes combination of tethering options available on this
+ * device.
*/
public static int getTetheringLabel(TetheringManager tm) {
String[] usbRegexs = tm.getTetherableUsbRegexs();
@@ -141,14 +153,12 @@ public class Utils {
}
}
- /**
- * Returns a label for the user, in the form of "User: user name" or "Work profile".
- */
+ /** Returns a label for the user, in the form of "User: user name" or "Work profile". */
public static String getUserLabel(Context context, UserInfo info) {
String name = info != null ? info.name : null;
if (info.isManagedProfile()) {
// We use predefined values for managed profiles
- return BuildCompatUtils.isAtLeastT()
+ return BuildCompatUtils.isAtLeastT()
? getUpdatableManagedUserTitle(context)
: context.getString(R.string.managed_user_title);
} else if (info.isGuest()) {
@@ -164,14 +174,14 @@ public class Utils {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static String getUpdatableManagedUserTitle(Context context) {
- return context.getSystemService(DevicePolicyManager.class).getResources().getString(
- WORK_PROFILE_USER_LABEL,
- () -> context.getString(R.string.managed_user_title));
+ return context.getSystemService(DevicePolicyManager.class)
+ .getResources()
+ .getString(
+ WORK_PROFILE_USER_LABEL,
+ () -> context.getString(R.string.managed_user_title));
}
- /**
- * Returns a circular icon for a user.
- */
+ /** Returns a circular icon for a user. */
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
final int iconSize = UserIconDrawable.getDefaultSize(context);
if (user.isManagedProfile()) {
@@ -185,12 +195,14 @@ public class Utils {
return new UserIconDrawable(iconSize).setIcon(icon).bake();
}
}
- return new UserIconDrawable(iconSize).setIconDrawable(
- UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+ return new UserIconDrawable(iconSize)
+ .setIconDrawable(
+ UserIcons.getDefaultUserIcon(
+ context.getResources(), user.id, /* light= */ false))
.bake();
}
- /** Formats a double from 0.0..100.0 with an option to round **/
+ /** Formats a double from 0.0..100.0 with an option to round */
public static String formatPercentage(double percentage, boolean round) {
final int localPercentage = round ? Math.round((float) percentage) : (int) percentage;
return formatPercentage(localPercentage);
@@ -222,23 +234,27 @@ public class Utils {
*
* @param context the context
* @param batteryChangedIntent battery broadcast intent received from {@link
- * Intent.ACTION_BATTERY_CHANGED}.
+ * Intent.ACTION_BATTERY_CHANGED}.
* @param compactStatus to present compact battery charging string if {@code true}
* @return battery status string
*/
- public static String getBatteryStatus(Context context, Intent batteryChangedIntent,
- boolean compactStatus) {
- final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
+ @NonNull
+ public static String getBatteryStatus(
+ @NonNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus) {
+ final int status =
+ batteryChangedIntent.getIntExtra(
+ BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
final Resources res = context.getResources();
String statusString = res.getString(R.string.battery_info_status_unknown);
final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
if (batteryStatus.isCharged()) {
- statusString = res.getString(compactStatus
- ? R.string.battery_info_status_full_charged
- : R.string.battery_info_status_full);
+ statusString =
+ res.getString(
+ compactStatus
+ ? R.string.battery_info_status_full_charged
+ : R.string.battery_info_status_full);
} else {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
if (compactStatus) {
@@ -246,12 +262,12 @@ public class Utils {
} else if (batteryStatus.isPluggedInWired()) {
switch (batteryStatus.getChargingSpeed(context)) {
case BatteryStatus.CHARGING_FAST:
- statusString = res.getString(
- R.string.battery_info_status_charging_fast);
+ statusString =
+ res.getString(R.string.battery_info_status_charging_fast);
break;
case BatteryStatus.CHARGING_SLOWLY:
- statusString = res.getString(
- R.string.battery_info_status_charging_slow);
+ statusString =
+ res.getString(R.string.battery_info_status_charging_slow);
break;
default:
statusString = res.getString(R.string.battery_info_status_charging);
@@ -311,7 +327,7 @@ public class Utils {
@ColorInt
public static int applyAlphaAttr(Context context, int attr, int inputColor) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
float alpha = ta.getFloat(0, 0);
ta.recycle();
return applyAlpha(alpha, inputColor);
@@ -320,7 +336,10 @@ public class Utils {
@ColorInt
public static int applyAlpha(float alpha, int inputColor) {
alpha *= Color.alpha(inputColor);
- return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+ return Color.argb(
+ (int) (alpha),
+ Color.red(inputColor),
+ Color.green(inputColor),
Color.blue(inputColor));
}
@@ -329,19 +348,17 @@ public class Utils {
return getColorAttrDefaultColor(context, attr, 0);
}
- /**
- * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
- */
+ /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
@ColorInt
public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
@ColorInt int colorAccent = ta.getColor(0, defValue);
ta.recycle();
return colorAccent;
}
public static ColorStateList getColorAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
ColorStateList stateList = null;
try {
stateList = ta.getColorStateList(0);
@@ -356,35 +373,38 @@ public class Utils {
}
public static int getThemeAttr(Context context, int attr, int defaultValue) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
int theme = ta.getResourceId(0, defaultValue);
ta.recycle();
return theme;
}
public static Drawable getDrawable(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
Drawable drawable = ta.getDrawable(0);
ta.recycle();
return drawable;
}
/**
- * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
- * preserves the alpha for a given drawable
- * @param color
- * @return a color matrix that uses the source alpha and given color
- */
+ * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
+ * preserves the alpha for a given drawable
+ *
+ * @return a color matrix that uses the source alpha and given color
+ */
public static ColorMatrix getAlphaInvariantColorMatrixForColor(@ColorInt int color) {
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
- ColorMatrix cm = new ColorMatrix(new float[] {
- 0, 0, 0, 0, r,
- 0, 0, 0, 0, g,
- 0, 0, 0, 0, b,
- 0, 0, 0, 1, 0 });
+ ColorMatrix cm =
+ new ColorMatrix(
+ new float[] {
+ 0, 0, 0, 0, r,
+ 0, 0, 0, 0, g,
+ 0, 0, 0, 0, b,
+ 0, 0, 0, 1, 0
+ });
return cm;
}
@@ -393,7 +413,7 @@ public class Utils {
* Create a ColorMatrixColorFilter to tint a drawable but retain its alpha characteristics
*
* @return a ColorMatrixColorFilter which changes the color of the output but is invariant on
- * the source alpha
+ * the source alpha
*/
public static ColorFilter getAlphaInvariantColorFilterForColor(@ColorInt int color) {
return new ColorMatrixColorFilter(getAlphaInvariantColorMatrixForColor(color));
@@ -402,16 +422,17 @@ public class Utils {
/**
* Determine whether a package is a "system package", in which case certain things (like
* disabling notifications or disabling the package altogether) should be disallowed.
- * <p>
- * Note: This function is just for UI treatment, and should not be used for security purposes.
*
- * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and
- * {@link #isEssentialPackage} instead.
+ * <p>Note: This function is just for UI treatment, and should not be used for security
+ * purposes.
+ *
+ * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and {@link
+ * #isEssentialPackage} instead.
*/
@Deprecated
public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
if (sSystemSignature == null) {
- sSystemSignature = new Signature[]{getSystemSignature(pm)};
+ sSystemSignature = new Signature[] {getSystemSignature(pm)};
}
return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
|| isEssentialPackage(resources, pm, pkg.packageName);
@@ -435,8 +456,8 @@ public class Utils {
/**
* Determine whether a package is a "essential package".
- * <p>
- * In which case certain things (like disabling the package) should be disallowed.
+ *
+ * <p>In which case certain things (like disabling the package) should be disallowed.
*/
public static boolean isEssentialPackage(
Resources resources, PackageManager pm, String packageName) {
@@ -462,14 +483,12 @@ public class Utils {
* returns {@code false}.
*/
public static boolean isDeviceProvisioningPackage(Resources resources, String packageName) {
- String deviceProvisioningPackage = resources.getString(
- com.android.internal.R.string.config_deviceProvisioningPackage);
+ String deviceProvisioningPackage =
+ resources.getString(com.android.internal.R.string.config_deviceProvisioningPackage);
return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
}
- /**
- * Fetch the package name of the default WebView provider.
- */
+ /** Fetch the package name of the default WebView provider. */
@Nullable
private static String getDefaultWebViewPackageName() {
if (sDefaultWebViewPackageName != null) {
@@ -503,8 +522,8 @@ public class Utils {
/**
* Returns the Wifi icon resource for a given RSSI level.
*
- * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
- * signal icon to users.
+ * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x signal
+ * icon to users.
* @param level The number of bars to show (0-4)
* @throws IllegalArgumentException if an invalid RSSI level is given.
*/
@@ -520,10 +539,7 @@ public class Utils {
try {
defaultDays =
resources.getInteger(
- com.android
- .internal
- .R
- .integer
+ com.android.internal.R.integer
.config_storageManagerDaystoRetainDefault);
} catch (Resources.NotFoundException e) {
// We are likely in a test environment.
@@ -535,7 +551,7 @@ public class Utils {
return !context.getSystemService(TelephonyManager.class).isDataCapable();
}
- /** Returns if the automatic storage management feature is turned on or not. **/
+ /** Returns if the automatic storage management feature is turned on or not. */
public static boolean isStorageManagerEnabled(Context context) {
boolean isDefaultOn;
try {
@@ -543,15 +559,14 @@ public class Utils {
} catch (Resources.NotFoundException e) {
isDefaultOn = false;
}
- return Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
- isDefaultOn ? 1 : 0)
+ return Settings.Secure.getInt(
+ context.getContentResolver(),
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+ isDefaultOn ? 1 : 0)
!= 0;
}
- /**
- * get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status.
- */
+ /** get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status. */
public static boolean isAudioModeOngoingCall(Context context) {
final AudioManager audioManager = context.getSystemService(AudioManager.class);
final int audioMode = audioManager.getMode();
@@ -561,8 +576,8 @@ public class Utils {
}
/**
- * Return the service state is in-service or not.
- * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+ * Return the service state is in-service or not. To make behavior consistent with SystemUI and
+ * Settings/AboutPhone/SIM status UI
*
* @param serviceState Service state. {@link ServiceState}
*/
@@ -581,13 +596,12 @@ public class Utils {
}
/**
- * Return the combined service state.
- * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+ * Return the combined service state. To make behavior consistent with SystemUI and
+ * Settings/AboutPhone/SIM status UI.
*
- * This method returns a single service state int if either the voice reg state is
- * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
- * WWAN transport type. We consider the combined service state of an IWLAN network
- * to be OOS.
+ * <p>This method returns a single service state int if either the voice reg state is {@link
+ * ServiceState#STATE_IN_SERVICE} or if data network is registered via a WWAN transport type. We
+ * consider the combined service state of an IWLAN network to be OOS.
*
* @param serviceState Service state. {@link ServiceState}
*/
@@ -618,9 +632,10 @@ public class Utils {
// on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
// query the WWAN network directly and check for its registration state
private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
- final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ final NetworkRegistrationInfo networkRegWwan =
+ serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
if (networkRegWwan == null) {
return false;
@@ -633,8 +648,8 @@ public class Utils {
public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
int userType = UserIconInfo.TYPE_MAIN;
try {
- UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
- user.getIdentifier());
+ UserInfo ui =
+ context.getSystemService(UserManager.class).getUserInfo(user.getIdentifier());
if (ui != null) {
if (ui.isCloneProfile()) {
userType = UserIconInfo.TYPE_CLONED;
@@ -650,15 +665,16 @@ public class Utils {
try (IconFactory iconFactory = IconFactory.obtain(context)) {
return iconFactory
.createBadgedIconBitmap(
- icon,
- new IconOptions().setUser(new UserIconInfo(user, userType)))
+ icon, new IconOptions().setUser(new UserIconInfo(user, userType)))
.newIcon(context);
}
}
/** Get the {@link Drawable} that represents the app icon */
public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
- return getBadgedIcon(context, appInfo.loadUnbadgedIcon(context.getPackageManager()),
+ return getBadgedIcon(
+ context,
+ appInfo.loadUnbadgedIcon(context.getPackageManager()),
UserHandle.getUserHandleForUid(appInfo.uid));
}
@@ -669,10 +685,11 @@ public class Utils {
* @param source bitmap to apply round corner.
* @param cornerRadius corner radius value.
*/
- public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
- @NonNull Bitmap source, @NonNull float cornerRadius) {
- final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
- Bitmap.Config.ARGB_8888);
+ @NonNull
+ public static Bitmap convertCornerRadiusBitmap(
+ @NonNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius) {
+ final Bitmap roundedBitmap =
+ Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
final RoundedBitmapDrawable drawable =
RoundedBitmapDrawableFactory.create(context.getResources(), source);
drawable.setAntiAlias(true);
@@ -687,9 +704,6 @@ public class Utils {
* Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
* input NetworkCapabilities is not for a VCN network with underlying WiFi network.
*
- * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated
- * off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}.
- *
* @param networkCapabilities NetworkCapabilities of the network.
*/
@Nullable
@@ -708,8 +722,9 @@ public class Utils {
// Avoid the caller doesn't have permission to read the "Settings.Secure" data.
try {
// Whether the incompatible charger warning is disabled or not
- if (Settings.Secure.getInt(context.getContentResolver(),
- INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0) == 1) {
+ if (Settings.Secure.getInt(
+ context.getContentResolver(), INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0)
+ == 1) {
Log.d(tag, "containsIncompatibleChargers: disabled");
return false;
}
@@ -718,8 +733,7 @@ public class Utils {
return false;
}
- final List<UsbPort> usbPortList =
- context.getSystemService(UsbManager.class).getPorts();
+ final List<UsbPort> usbPortList = context.getSystemService(UsbManager.class).getPorts();
if (usbPortList == null || usbPortList.isEmpty()) {
return false;
}
@@ -760,4 +774,85 @@ public class Utils {
return false;
}
+ /** Whether to show the wireless charging notification. */
+ public static boolean shouldShowWirelessChargingNotification(
+ @NonNull Context context, @NonNull String tag) {
+ try {
+ return shouldShowWirelessChargingNotificationInternal(context, tag);
+ } catch (Exception e) {
+ Log.e(tag, "shouldShowWirelessChargingNotification()", e);
+ return false;
+ }
+ }
+
+ /** Stores the timestamp of the wireless charging notification. */
+ public static void updateWirelessChargingNotificationTimestamp(
+ @NonNull Context context, long timestamp, @NonNull String tag) {
+ try {
+ Secure.putLong(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ timestamp);
+ } catch (Exception e) {
+ Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
+ }
+ }
+
+ /** Whether to show the wireless charging warning in Settings. */
+ public static boolean shouldShowWirelessChargingWarningTip(
+ @NonNull Context context, @NonNull String tag) {
+ try {
+ return Secure.getInt(context.getContentResolver(), WIRELESS_CHARGING_WARNING_ENABLED, 0)
+ == 1;
+ } catch (Exception e) {
+ Log.e(tag, "shouldShowWirelessChargingWarningTip()", e);
+ }
+ return false;
+ }
+
+ /** Stores the state of whether the wireless charging warning in Settings is enabled. */
+ public static void updateWirelessChargingWarningEnabled(
+ @NonNull Context context, boolean enabled, @NonNull String tag) {
+ try {
+ Secure.putInt(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_WARNING_ENABLED,
+ enabled ? 1 : 0);
+ } catch (Exception e) {
+ Log.e(tag, "setWirelessChargingWarningEnabled()", e);
+ }
+ }
+
+ private static boolean shouldShowWirelessChargingNotificationInternal(
+ @NonNull Context context, @NonNull String tag) {
+ final long lastNotificationTimeMillis =
+ Secure.getLong(
+ context.getContentResolver(),
+ WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
+ return false;
+ }
+ if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
+ updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+ updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+ return true;
+ }
+ final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
+ final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
+ Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
+ if (show) {
+ updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+ updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+ }
+ return show;
+ }
+
+ private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
+ return lastNotificationTimeMillis == Long.MIN_VALUE;
+ }
+
+ private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
+ return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index a88a9c72e4a9..2d07e5d471ed 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib;
import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
+import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
+import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
import static com.google.common.truth.Truth.assertThat;
@@ -28,10 +31,10 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
import android.location.LocationManager;
import android.media.AudioManager;
import android.os.BatteryManager;
@@ -59,6 +62,7 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -72,21 +76,16 @@ public class UtilsTest {
private static final String PERCENTAGE_49 = "49%";
private static final String PERCENTAGE_50 = "50%";
private static final String PERCENTAGE_100 = "100%";
+ private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
private AudioManager mAudioManager;
private Context mContext;
- @Mock
- private LocationManager mLocationManager;
- @Mock
- private ServiceState mServiceState;
- @Mock
- private NetworkRegistrationInfo mNetworkRegistrationInfo;
- @Mock
- private UsbPort mUsbPort;
- @Mock
- private UsbManager mUsbManager;
- @Mock
- private UsbPortStatus mUsbPortStatus;
+ @Mock private LocationManager mLocationManager;
+ @Mock private ServiceState mServiceState;
+ @Mock private NetworkRegistrationInfo mNetworkRegistrationInfo;
+ @Mock private UsbPort mUsbPort;
+ @Mock private UsbManager mUsbManager;
+ @Mock private UsbPortStatus mUsbPortStatus;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -102,27 +101,37 @@ public class UtilsTest {
@After
public void reset() {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
}
@Test
public void testUpdateLocationEnabled() {
int currentUserId = ActivityManager.getCurrentUser();
- Utils.updateLocationEnabled(mContext, true, currentUserId,
- Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ Utils.updateLocationEnabled(
+ mContext, true, currentUserId, Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
- assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_CHANGER,
- Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
- Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ assertThat(
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_CHANGER,
+ Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+ .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
- final String[] expectedPercentages =
- {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
- PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
+ final String[] expectedPercentages = {
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_1,
+ PERCENTAGE_1,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_50,
+ PERCENTAGE_50,
+ PERCENTAGE_100
+ };
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -132,9 +141,17 @@ public class UtilsTest {
@Test
public void testFormatPercentage_RoundFalse_NoRound() {
- final String[] expectedPercentages =
- {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
- PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
+ final String[] expectedPercentages = {
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_0,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_49,
+ PERCENTAGE_50,
+ PERCENTAGE_100
+ };
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -146,7 +163,9 @@ public class UtilsTest {
public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
Resources resources = mock(Resources.class);
when(resources.getInteger(
- eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
+ eq(
+ com.android.internal.R.integer
+ .config_storageManagerDaystoRetainDefault)))
.thenReturn(60);
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
}
@@ -214,8 +233,10 @@ public class UtilsTest {
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
assertThat(Utils.isInService(mServiceState)).isTrue();
@@ -224,8 +245,10 @@ public class UtilsTest {
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
@@ -235,8 +258,10 @@ public class UtilsTest {
@Test
public void isInService_voiceOutOfServiceDataNull_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(null);
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -244,8 +269,10 @@ public class UtilsTest {
@Test
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
assertThat(Utils.isInService(mServiceState)).isFalse();
@@ -260,96 +287,106 @@ public class UtilsTest {
@Test
public void getCombinedServiceState_servicestateNull_returnOutOfService() {
- assertThat(Utils.getCombinedServiceState(null)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(null))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_POWER_OFF);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_POWER_OFF);
}
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_IN_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_IN_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_IN_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_IN_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+ .thenReturn(mNetworkRegistrationInfo);
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(
- ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataRegistrationState())
+ .thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
- ServiceState.STATE_OUT_OF_SERVICE);
+ assertThat(Utils.getCombinedServiceState(mServiceState))
+ .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
}
@Test
public void getBatteryStatus_statusIsFull_returnFullString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
- BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_full));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full));
}
@Test
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
- BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+ .putExtra(BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_full_charged));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
}
@Test
public void getBatteryStatus_batteryLevelIs100_returnFullString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_FULL);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_full));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full));
}
@Test
public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
- final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_FULL);
+ final Intent intent =
+ new Intent()
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_full_charged));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
}
@Test
@@ -359,8 +396,8 @@ public class UtilsTest {
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -370,8 +407,8 @@ public class UtilsTest {
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging_dock));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging_dock));
}
@Test
@@ -381,8 +418,8 @@ public class UtilsTest {
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging_wireless));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging_wireless));
}
@Test
@@ -392,8 +429,8 @@ public class UtilsTest {
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -403,8 +440,8 @@ public class UtilsTest {
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
final Resources resources = mContext.getResources();
- assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
- resources.getString(R.string.battery_info_status_charging));
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+ .isEqualTo(resources.getString(R.string.battery_info_status_charging));
}
@Test
@@ -503,12 +540,97 @@ public class UtilsTest {
@Test
public void containsIncompatibleChargers_disableWarning_returnFalse() {
setupIncompatibleCharging();
- Settings.Secure.putInt(mContext.getContentResolver(),
- Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
}
+ @Test
+ public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
+ updateWirelessChargingNotificationTimestamp(
+ mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
+ updateWirelessChargingNotificationTimestamp(
+ mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+ Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp())
+ .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
+ final long monthAgo = Duration.ofDays(31).toMillis();
+ final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+ updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+ assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
+ final long monthAgo = Duration.ofDays(31).toMillis();
+ final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+ updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+ Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
+ updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
+ }
+
+ @Test
+ public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
+ updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+ assertThat(getWirelessChargingNotificationTimestamp())
+ .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+ assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
+ }
+
+ @Test
+ public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
+ Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
+
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void shouldShowWirelessChargingWarningTip_disabled_returnFalse() {
+ Utils.updateWirelessChargingWarningEnabled(mContext, false, TAG);
+
+ assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isFalse();
+ }
+
private void setupIncompatibleCharging() {
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
}
@@ -520,6 +642,13 @@ public class UtilsTest {
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
+ }
+
+ private long getWirelessChargingNotificationTimestamp() {
+ return Settings.Secure.getLong(
+ mContext.getContentResolver(),
+ Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+ WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ae71cece803f..1c9e748c5f3a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -614,8 +614,8 @@ final class SettingsState {
final Iterator<Map.Entry<String, Setting>> iterator = mSettings.entrySet().iterator();
int index = prefix.lastIndexOf('/');
String namespace = index < 0 ? "" : prefix.substring(0, index);
- Map<String, String> trunkFlagMap =
- mNamespaceDefaults.get(namespace);
+ Map<String, String> trunkFlagMap = (mNamespaceDefaults == null)
+ ? null : mNamespaceDefaults.get(namespace);
// Delete old keys with the prefix that are not part of the new set.
// trunk flags will not be configured with restricted propagation
// trunk flags will be explicitly set, so not removing them here
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 3fb825471c57..d70f82fe8ab7 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
@@ -61,11 +61,11 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
-import com.android.compose.ui.util.lerp
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index b26194f2397b..37d763bf95f7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -30,7 +30,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.lerp
-import com.android.compose.ui.util.lerp
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
/**
* A [State] whose [value] is animated.
@@ -282,7 +283,7 @@ private fun <T> valueOrNull(
} else {
val progress =
if (canOverflow) transition.progress
- else transition.progress.coerceIn(0f, 1f)
+ else transition.progress.fastCoerceIn(0f, 1f)
lerp(fromValue, toValue, progress)
}
} else fromValue ?: toValue
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e40f6b66eff4..828e34da378d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -39,6 +39,8 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
@@ -362,7 +364,7 @@ private fun elementAlpha(
isSpecified = { true },
::lerp,
)
- .coerceIn(0f, 1f)
+ .fastCoerceIn(0f, 1f)
}
@OptIn(ExperimentalComposeUiApi::class)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 529fc0327fcb..3ff869b5fdad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -283,21 +283,28 @@ private suspend fun PointerInputScope.detectDragGestures(
}
onDragStart(drag.position, overSlop, pressed.size)
- onDrag(drag, overSlop)
- val successful =
- when (orientation) {
- Orientation.Horizontal ->
- horizontalDrag(drag.id) {
- onDrag(it, it.positionChange().x)
- it.consume()
- }
- Orientation.Vertical ->
- verticalDrag(drag.id) {
- onDrag(it, it.positionChange().y)
- it.consume()
- }
- }
+ val successful: Boolean
+ try {
+ onDrag(drag, overSlop)
+
+ successful =
+ when (orientation) {
+ Orientation.Horizontal ->
+ horizontalDrag(drag.id) {
+ onDrag(it, it.positionChange().x)
+ it.consume()
+ }
+ Orientation.Vertical ->
+ verticalDrag(drag.id) {
+ onDrag(it, it.positionChange().y)
+ it.consume()
+ }
+ }
+ } catch (t: Throwable) {
+ onDragCancel()
+ throw t
+ }
if (successful) {
onDragEnd()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 23af5acd0ee8..b3d2bc994c08 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -701,27 +701,19 @@ internal class SceneNestedScrollHandler(
gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
- val swipeTransition = gestureHandler.swipeTransition
- val progress = swipeTransition.progress
val threshold = layoutImpl.transitionInterceptionThreshold
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- // The transition is always between 0 and 1. If it is close to either of these
- // intervals, we want to go directly to the TransitionState.Idle.
- // The progress value can go beyond this range in the case of overscroll.
- val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
- if (shouldSnapToIdle) {
- swipeTransition.cancelOffsetAnimation()
- layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
+ val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
+ if (hasSnappedToIdle) {
+ // If the current swipe transition is closed to 0f or 1f, then we want to
+ // interrupt the transition (snapping it to Idle) and scroll the list.
+ return@PriorityNestedScrollConnection false
}
- // Start only if we cannot consume this event
- val canStart = !shouldSnapToIdle
- if (canStart) {
- isIntercepting = true
- }
-
- canStart
+ // If the current swipe transition is *not* closed to 0f or 1f, then we want the
+ // scroll events to intercept the current transition to continue the scene
+ // transition.
+ isIntercepting = true
+ true
},
canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
val behavior: NestedScrollBehavior =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index aee6f9e15620..a8da55101548 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -24,6 +24,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -100,8 +105,9 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
- return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+ return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
}
/**
@@ -120,9 +126,12 @@ fun updateSceneTransitionLayoutState(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
- return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
- .apply { update(currentScene, onChangeScene, transitions) }
+ return remember {
+ HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+ }
+ .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
}
@Stable
@@ -183,8 +192,10 @@ sealed interface TransitionState {
}
}
-internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
- SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ protected var stateLinks: List<StateLink>,
+) : SceneTransitionLayoutState {
override var transitionState: TransitionState by
mutableStateOf(TransitionState.Idle(initialScene))
protected set
@@ -195,6 +206,8 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
/**
* Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
*
@@ -223,19 +236,94 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
transitions
.transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
-
+ cancelActiveTransitionLinks()
+ setupTransitionLinks(transition)
transitionState = transition
}
+ private fun cancelActiveTransitionLinks() {
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+ }
+ activeTransitionLinks.clear()
+ }
+
+ private fun setupTransitionLinks(transitionState: TransitionState) {
+ if (transitionState !is TransitionState.Transition) return
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
+
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
+
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transitionState,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ )
+
+ stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey)
+ activeTransitionLinks[stateLink] = linkedTransition
+ }
+ }
+
/**
* Notify that [transition] was finished and that we should settle to [idleScene]. This will do
* nothing if [transition] was interrupted since it was started.
*/
internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+ resolveActiveTransitionLinks(idleScene)
if (transitionState == transition) {
transitionState = TransitionState.Idle(idleScene)
}
}
+
+ private fun resolveActiveTransitionLinks(idleScene: SceneKey) {
+ val previousTransition = this.transitionState as? TransitionState.Transition ?: return
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ if (previousTransition.fromScene == idleScene) {
+ // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ } else if (previousTransition.toScene == idleScene) {
+ // The transition ended by arriving at the toScene, move link to Idle(toScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.toScene)
+ } else {
+ // The transition was interrupted by something else, we reset to initial state.
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ }
+ }
+ activeTransitionLinks.clear()
+ }
+
+ /**
+ * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
+ * to the closest scene.
+ *
+ * @return true if snapped to the closest scene.
+ */
+ internal fun snapToIdleIfClose(threshold: Float): Boolean {
+ val transition = currentTransition ?: return false
+ val progress = transition.progress
+ fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
+
+ return when {
+ isProgressCloseTo(0f) -> {
+ finishTransition(transition, transition.fromScene)
+ true
+ }
+ isProgressCloseTo(1f) -> {
+ finishTransition(transition, transition.toScene)
+ true
+ }
+ else -> false
+ }
+ }
}
/**
@@ -246,7 +334,8 @@ internal class HoistedSceneTransitionLayoutScene(
initialScene: SceneKey,
override var transitions: SceneTransitions,
private var changeScene: (SceneKey) -> Unit,
-) : BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
@@ -256,10 +345,12 @@ internal class HoistedSceneTransitionLayoutScene(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions,
+ stateLinks: List<StateLink>,
) {
SideEffect {
this.changeScene = onChangeScene
this.transitions = transitions
+ this.stateLinks = stateLinks
targetSceneChannel.trySend(currentScene)
}
@@ -283,7 +374,8 @@ internal class HoistedSceneTransitionLayoutScene(
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions,
-) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
override fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 04254fbb588b..603f7ba947c4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -16,6 +16,9 @@
package com.android.compose.animation.scene.transformation
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
@@ -106,10 +109,10 @@ data class TransformationRange(
fun progress(transitionProgress: Float): Float {
return when {
start.isSpecified() && end.isSpecified() ->
- ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+ ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
!start.isSpecified() && !end.isSpecified() -> transitionProgress
- end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
- else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+ end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+ else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
new file mode 100644
index 000000000000..33b57b25fd10
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A linked transition which is driven by a [originalTransition]. */
+internal class LinkedTransition(
+ private val originalTransition: TransitionState.Transition,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+) : TransitionState.Transition(fromScene, toScene) {
+
+ override val currentScene: SceneKey
+ get() {
+ return when (originalTransition.currentScene) {
+ originalTransition.fromScene -> fromScene
+ originalTransition.toScene -> toScene
+ else -> error("Original currentScene is neither FromScene nor ToScene")
+ }
+ }
+
+ override val isInitiatedByUserInput: Boolean
+ get() = originalTransition.isInitiatedByUserInput
+
+ override val isUserInputOngoing: Boolean
+ get() = originalTransition.isUserInputOngoing
+
+ override val progress: Float
+ get() = originalTransition.progress
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
new file mode 100644
index 000000000000..6c299463f978
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
+class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
+
+ internal val target = target as BaseSceneTransitionLayoutState
+
+ /**
+ * Links two transitions (source and target) together.
+ *
+ * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
+ * `null`, `SceneA` means that any transition at the source will trigger a transition in the
+ * target to `SceneA` from any current scene.
+ */
+ class TransitionLink(
+ val sourceFrom: SceneKey?,
+ val sourceTo: SceneKey?,
+ val targetFrom: SceneKey?,
+ val targetTo: SceneKey,
+ val targetTransitionKey: TransitionKey? = null,
+ ) {
+ init {
+ if (
+ (sourceFrom != null && sourceFrom == sourceTo) ||
+ (targetFrom != null && targetFrom == targetTo)
+ )
+ error("From and To can't be the same")
+ }
+
+ internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
+ return (sourceFrom == null || sourceFrom == transition.fromScene) &&
+ (sourceTo == null || sourceTo == transition.toScene)
+ }
+
+ internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentScene) &&
+ targetTo != targetCurrentScene
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index 13747b72724e..e78ab297bfba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -20,24 +20,8 @@ package com.android.compose.ui.util
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.Scale
-import kotlin.math.roundToInt
-import kotlin.math.roundToLong
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Float, stop: Float, fraction: Float): Float {
- return (1 - fraction) * start + fraction * stop
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Int, stop: Int, fraction: Float): Int {
- return start + ((stop - start) * fraction.toDouble()).roundToInt()
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Long, stop: Long, fraction: Float): Long {
- return start + ((stop - start) * fraction.toDouble()).roundToLong()
-}
/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a116501a298c..e8854cf0de60 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -30,8 +30,8 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.ui.util.lerp
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Rule
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
new file mode 100644
index 000000000000..cd99d05158cd
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MultiPointerDraggableTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun cancellingPointerCallsOnDragStopped() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+
+ var enabled by mutableStateOf(false)
+ var started = false
+ var dragged = false
+ var stopped = false
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { enabled },
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ -> started = true },
+ onDragDelta = { _ -> dragged = true },
+ onDragStopped = { stopped = true },
+ )
+ )
+ }
+
+ fun startDraggingDown() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun releaseFinger() {
+ rule.onRoot().performTouchInput { up() }
+ }
+
+ // Swiping down does nothing because enabled is false.
+ startDraggingDown()
+ assertThat(started).isFalse()
+ assertThat(dragged).isFalse()
+ assertThat(stopped).isFalse()
+ releaseFinger()
+
+ // Enable the draggable and swipe down. This should both call onDragStarted() and
+ // onDragDelta().
+ enabled = true
+ rule.waitForIdle()
+ startDraggingDown()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isFalse()
+
+ // Disable the pointer input. This should call onDragStopped() even if didn't release the
+ // finger yet.
+ enabled = false
+ rule.waitForIdle()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isTrue()
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c61917dbfb17..f81a7f2908e8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,10 +18,14 @@ package com.android.compose.animation.scene
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.transition.link.StateLink
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -31,93 +35,241 @@ import org.junit.runner.RunWith
class SceneTransitionLayoutStateTest {
@get:Rule val rule = createComposeRule()
+ class TestableTransition(
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ ) : TransitionState.Transition(fromScene, toScene) {
+ override var currentScene: SceneKey = fromScene
+ override var progress: Float = 0.0f
+ override var isInitiatedByUserInput: Boolean = false
+ override var isUserInputOngoing: Boolean = false
+ }
+
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
- .isFalse()
+ assertThat(state.isTransitioning(from = SceneA)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse()
}
@Test
fun isTransitioningTo_transition() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- transitionKey = null
- )
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null)
assertThat(state.isTransitioning()).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
- assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.isTransitioning(from = SceneA)).isTrue()
+ assertThat(state.isTransitioning(from = SceneB)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isTrue()
+ assertThat(state.isTransitioning(to = SceneA)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
}
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
assertThat(transition).isNotNull()
assertThat(state.transitionState).isEqualTo(transition)
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
// Progress is 0f, so we don't animate at all and directly snap back to A.
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
}
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ val state = MutableSceneTransitionLayoutState(SceneA)
lateinit var transition: TransitionState.Transition
val job =
launch(start = CoroutineStart.UNDISPATCHED) {
- transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+ transition = state.setTargetScene(SceneB, coroutineScope = this)!!
}
assertThat(state.transitionState).isEqualTo(transition)
// Cancelling the scope/job still sets the state to Idle(targetScene).
job.cancel()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ }
+
+ private fun setupLinkedStates(
+ parentInitialScene: SceneKey = SceneC,
+ childInitialScene: SceneKey = SceneA,
+ sourceFrom: SceneKey? = SceneA,
+ sourceTo: SceneKey? = SceneB,
+ targetFrom: SceneKey? = SceneC,
+ targetTo: SceneKey = SceneD
+ ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+ val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo))
+ )
+ )
+ val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
+ return Pair(
+ parentState as BaseSceneTransitionLayoutState,
+ childState as BaseSceneTransitionLayoutState
+ )
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_transitiveLink() {
+ val parentParentState =
+ MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+ val parentLink =
+ listOf(
+ StateLink(
+ parentParentState,
+ listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC))
+ )
+ )
+ val parentState =
+ MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
+ as BaseSceneTransitionLayoutState
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD))
+ )
+ )
+ val childState =
+ MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
+ as BaseSceneTransitionLayoutState
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+ assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_linkProgressIsEqual() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
+
+ childTransition.progress = .5f
+ assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
+ }
+
+ @Test
+ fun linkedTransition_reverseTransitionIsNotLinked() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneB, SceneA)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneD)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ val parentTransition = TestableTransition(SceneC, SceneA)
+ childState.startTransition(childTransition, null)
+ parentState.startTransition(parentTransition, null)
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(parentTransition)
}
@Test
@@ -125,11 +277,11 @@ class SceneTransitionLayoutStateTest {
val transitionkey = TransitionKey(debugName = "foo")
val state =
MutableSceneTransitionLayoutState(
- TestScenes.SceneA,
+ SceneA,
transitions =
transitions {
- from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
- from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+ from(SceneA, to = SceneB, key = transitionkey) {
fade(TestElements.Foo)
fade(TestElements.Bar)
}
@@ -138,19 +290,19 @@ class SceneTransitionLayoutStateTest {
as MutableSceneTransitionLayoutStateImpl
// Default transition from A to B.
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(1)
// Go back to A.
- state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ state.setTargetScene(SceneA, coroutineScope = this)
testScheduler.advanceUntilIdle()
assertThat(state.currentTransition).isNull()
- assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
// Specific transition from A to B.
assertThat(
state.setTargetScene(
- TestScenes.SceneB,
+ SceneB,
coroutineScope = this,
transitionKey = transitionkey,
)
@@ -158,4 +310,83 @@ class SceneTransitionLayoutStateTest {
.isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(2)
}
+
+ @Test
+ fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the initial scene if it is close to 0.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ }
+
+ @Test
+ fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the final scene if it is close to 1.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+ val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreNotMatched() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 754d5dc0c9c6..2a87452b0b6a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -27,7 +27,11 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
-/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+/**
+ * Defines interface for classes that can provide access to data from [Settings.Secure].
+ * This repository doesn't guarantee to provide value across different users. For that
+ * see: [UserAwareSecureSettingsRepository]
+ */
interface SecureSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 15190214e06e..c6327ffa25f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,11 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -90,6 +92,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
.thenReturn(mock(ImageView::class.java))
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
+
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
@@ -111,6 +115,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
postureController,
fakeFeatureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5f932f452751..e8a43ac0ad6c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -51,6 +52,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -202,6 +204,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -232,6 +235,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
postureController,
featureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6cc680baf938..2de013bc7abc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,7 +59,10 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRootViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+ }
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val keyguardInteractor = kosmos.keyguardInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index b60f483cccbb..63fb67d432f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -50,7 +50,8 @@ class FlashlightMapperTest : SysuiTestCase() {
@Test
fun mapsDisabledDataToInactiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val actualActivationState = tileState.activationState
@@ -59,7 +60,8 @@ class FlashlightMapperTest : SysuiTestCase() {
@Test
fun mapsEnabledDataToActiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val actualActivationState = tileState.activationState
assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
@@ -67,7 +69,8 @@ class FlashlightMapperTest : SysuiTestCase() {
@Test
fun mapsEnabledDataToOnIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
@@ -77,7 +80,8 @@ class FlashlightMapperTest : SysuiTestCase() {
@Test
fun mapsDisabledDataToOffIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
@@ -86,11 +90,32 @@ class FlashlightMapperTest : SysuiTestCase() {
}
@Test
- fun supportsOnlyClickAction() {
+ fun mapsUnavailableDataToOffIconState() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+ val actualIcon = tileState.icon()
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ }
+
+ @Test
+ fun supportClickActionWhenAvailable() {
val dontCare = true
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(dontCare))
val supportedActions = tileState.supportedActions
assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
}
+
+ @Test
+ fun doesNotSupportClickActionWhenUnavailable() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val supportedActions = tileState.supportedActions
+ assertThat(supportedActions).isEmpty()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index a9e39354d131..c5a8c70330a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -70,8 +70,7 @@ class FlashlightTileDataInteractorTest : SysuiTestCase() {
}
@Test
- fun dataMatchesController() = runTest {
- controller.setFlashlight(false)
+ fun isEnabledDataMatchesControllerWhenAvailable() = runTest {
val flowValues: List<FlashlightTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
@@ -81,8 +80,35 @@ class FlashlightTileDataInteractorTest : SysuiTestCase() {
controller.setFlashlight(false)
runCurrent()
- assertThat(flowValues.size).isEqualTo(3)
- assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup(), 2 from this test
+ assertThat(
+ flowValues.filterIsInstance<FlashlightTileModel.FlashlightAvailable>().map {
+ it.isEnabled
+ }
+ )
+ .containsExactly(false, false, true, false)
+ .inOrder()
+ }
+
+ /**
+ * Simulates the scenario of changes in flashlight tile availability when camera is initially
+ * closed, then opened, and closed again.
+ */
+ @Test
+ fun availabilityDataMatchesControllerAvailability() = runTest {
+ val flowValues: List<FlashlightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(false)
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(true)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup + 2 from this test
+ assertThat(flowValues.map { it is FlashlightTileModel.FlashlightAvailable })
+ .containsExactly(true, true, false, true)
+ .inOrder()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index 28d43b369dd4..1f19c98a93a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -29,7 +29,9 @@ import org.junit.Assume.assumeFalse
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.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
@@ -51,7 +53,7 @@ class FlashlightTileUserActionInteractorTest : SysuiTestCase() {
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = false
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
@@ -61,8 +63,17 @@ class FlashlightTileUserActionInteractorTest : SysuiTestCase() {
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = true
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
+
+ @Test
+ fun handleClickWhenUnavailable() = runTest {
+ assumeFalse(ActivityManager.isUserAMonkey())
+
+ underTest.handleInput(click(FlashlightTileModel.FlashlightTemporarilyUnavailable))
+
+ verify(controller, never()).setFlashlight(anyBoolean())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 189ba7b1965a..9d3f0d6a93f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,6 +265,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+ centralSurfaces = mock(),
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 12dbf11255b6..34c5173436c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.startable
+import android.app.StatusBarManager
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
import android.view.Display
@@ -48,6 +49,7 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -65,6 +67,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
@@ -78,6 +81,7 @@ import org.mockito.MockitoAnnotations
class SceneContainerStartableTest : SysuiTestCase() {
@Mock private lateinit var windowController: NotificationShadeWindowController
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -115,6 +119,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+ centralSurfaces = centralSurfaces,
)
}
@@ -763,6 +768,227 @@ class SceneContainerStartableTest : SysuiTestCase() {
verify(windowController, times(2)).setNotificationShadeFocusable(false)
}
+ @Test
+ fun hydrateInteractionState_whileLocked() =
+ testScope.runTest {
+ val transitionStateFlow =
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ underTest.start()
+ runCurrent()
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ false,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ true,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ false,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ true,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
+ fun hydrateInteractionState_whileUnlocked() =
+ testScope.runTest {
+ val transitionStateFlow =
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ underTest.start()
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ private fun TestScope.emulateSceneTransition(
+ transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+ toScene: SceneKey,
+ verifyBeforeTransition: (() -> Unit)? = null,
+ verifyDuringTransition: (() -> Unit)? = null,
+ verifyAfterTransition: (() -> Unit)? = null,
+ ) {
+ val fromScene = sceneInteractor.desiredScene.value.key
+ sceneInteractor.changeScene(SceneModel(toScene), "reason")
+ runCurrent()
+ verifyBeforeTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = fromScene,
+ toScene = toScene,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ verifyDuringTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ scene = toScene,
+ )
+ runCurrent()
+ verifyAfterTransition?.invoke()
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
new file mode 100644
index 000000000000..d3c6598296a6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.content.applicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.recents.utilities.Utilities
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeBackActionInteractorImplTest : SysuiTestCase() {
+ val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ val testScope = kosmos.testScope
+ val sceneInteractor = kosmos.sceneInteractor
+ val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ val underTest = kosmos.shadeBackActionInteractor
+
+ @Before
+ fun ignoreSplitShade() {
+ Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
+ }
+
+ @Test
+ fun animateCollapseQs_notOnQs() =
+ testScope.runTest {
+ setScene(SceneKey.Shade)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_entered() =
+ testScope.runTest {
+ enterDevice()
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_locked() =
+ testScope.runTest {
+ deviceEntryRepository.setUnlocked(false)
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseQs_notFullyCollapse() =
+ testScope.runTest {
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(false)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ private fun enterDevice() {
+ deviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ setScene(SceneKey.Gone)
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
new file mode 100644
index 000000000000..ad228943c989
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="oval">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </item>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
new file mode 100644
index 000000000000..8c2b03650560
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <stroke android:width="3dp"
+ android:color="@color/bouncer_password_focus_color" />
+ <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/>
+ </shape>
+ </item>
+ <item>
+ <inset android:insetLeft="-4dp"
+ android:insetRight="-4dp"
+ android:insetTop="-4dp">
+ <shape android:shape="rectangle">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </inset>
+ </item>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
index 84b89ca68e65..84b89ca68e65 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 2fc1d2ec5807..909d4fca5018 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,7 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
- android:gravity="center_horizontal"
+ android:gravity="center"
android:singleLine="true"
android:textStyle="normal"
android:inputType="textPassword"
@@ -68,14 +68,14 @@
<ImageView android:id="@+id/switch_ime_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="12dp"
android:src="@drawable/ic_lockscreen_ime"
android:contentDescription="@string/accessibility_ime_switch_button"
android:clickable="true"
- android:padding="8dip"
+ android:layout_marginRight="8dp"
+ android:padding="12dip"
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="end|center_vertical"
- android:background="?android:attr/selectableItemBackground"
+ android:background="@drawable/bouncer_input_method_background"
android:visibility="gone"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index ddad1e3f8940..e853f02ef510 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -28,8 +28,14 @@
<!-- Width for the keyguard pin input field -->
<dimen name="keyguard_pin_field_width">292dp</dimen>
- <!-- Width for the keyguard pin input field -->
- <dimen name="keyguard_pin_field_height">48dp</dimen>
+ <!-- height for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_height">56dp</dimen>
+
+ <!-- height for the keyguard password input field -->
+ <dimen name="keyguard_password_field_height">56dp</dimen>
+
+ <!-- width for the keyguard password input field -->
+ <dimen name="keyguard_password_field_width">276dp</dimen>
<!-- Height of the sliding KeyguardSecurityContainer
(includes 2x keyguard_security_view_top_margin) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4789a229c4d0..c43e394cb97a 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,7 @@
</style>
<style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
+ <item name="android:background">@drawable/bouncer_pin_view_focused_background</item>
<item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 51012a4f3a21..cc317544de9b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,7 +174,7 @@
<dimen name="status_bar_clock_size">14sp</dimen>
<!-- The starting padding for the clock in the status bar. -->
- <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+ <dimen name="status_bar_clock_starting_padding">4dp</dimen>
<!-- The end padding for the clock in the status bar. -->
<dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -395,7 +395,7 @@
<dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
<!-- the padding on the start of the statusbar -->
- <dimen name="status_bar_padding_start">8dp</dimen>
+ <dimen name="status_bar_padding_start">4dp</dimen>
<!-- the padding on the end of the statusbar -->
<dimen name="status_bar_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index efd8f7f97ca3..1a10c7aeb573 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,6 +30,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.Flags;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.ui.BouncerMessageView;
@@ -212,6 +213,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
private final KeyboardRepository mKeyboardRepository;
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -226,7 +228,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
UiEventLogger uiEventLogger,
- KeyboardRepository keyboardRepository) {
+ KeyboardRepository keyboardRepository,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -244,6 +247,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
mKeyboardRepository = keyboardRepository;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -265,7 +269,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyguardKeyboardInteractor);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 3d8aaaf6f13f..7473e0c62d1d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -17,8 +17,10 @@
package com.android.keyguard;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.Editable;
import android.text.InputType;
@@ -27,6 +29,7 @@ import android.text.TextWatcher;
import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
@@ -39,6 +42,8 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -52,6 +57,7 @@ import java.util.List;
public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -60,6 +66,8 @@ public class KeyguardPasswordViewController
private final DelayableExecutor mMainExecutor;
private final KeyguardViewController mKeyguardViewController;
private final boolean mShowImeAtScreenOn;
+ private Drawable mDefaultPasswordFieldBackground;
+ private Drawable mFocusedPasswordFieldBackground;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
private boolean mPaused;
@@ -121,7 +129,8 @@ public class KeyguardPasswordViewController
KeyguardViewController keyguardViewController,
DevicePostureController postureController,
FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
@@ -130,11 +139,15 @@ public class KeyguardPasswordViewController
mPostureController = postureController;
mMainExecutor = mainExecutor;
mKeyguardViewController = keyguardViewController;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
view.setIsLockScreenLandscapeEnabled();
}
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
+ mFocusedPasswordFieldBackground = getResources().getDrawable(
+ R.drawable.bouncer_password_view_background);
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
}
@@ -175,6 +188,27 @@ public class KeyguardPasswordViewController
// If there's more than one IME, enable the IME switcher button
updateSwitchImeButton();
+
+ if (Flags.pinInputFieldStyledFocusState()) {
+ collectFlow(mPasswordEntry,
+ mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
+ this::setPasswordFieldFocusBackground);
+
+ ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+ layoutParams.height = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_height);
+ layoutParams.width = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_width);
+ }
+
+ }
+
+ private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
+ if (isAnyKeyboardConnected) {
+ mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
+ } else {
+ mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ef6514447561..9ebae9023d44 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -109,8 +109,12 @@ public class KeyguardVisibilityHelper {
animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
- PropertyAnimator.setProperty(
- mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 1");
+ } else {
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ }
} else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
mView.setVisibility(View.VISIBLE);
mKeyguardViewVisibilityAnimating = true;
@@ -179,9 +183,13 @@ public class KeyguardVisibilityHelper {
mView.setVisibility(View.VISIBLE);
}
} else {
- log("Direct set Visibility to GONE");
- mView.setVisibility(View.GONE);
- mView.setAlpha(1f);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 2");
+ } else {
+ log("Direct set Visibility to GONE");
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
}
mLastOccludedState = isOccluded;
diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
new file mode 100644
index 000000000000..c39d3e66038c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) {
+ val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 6076f32486b9..5bd7e5455c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -29,7 +29,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@ constructor(
}
override fun onBackProgressed(backEvent: BackEvent) {
- if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
- shadeViewController.onBackProgressed(backEvent.progress)
+ if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
+ shadeBackActionInteractor.onBackProgressed(backEvent.progress)
}
}
}
@@ -77,12 +77,12 @@ constructor(
get() =
notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
- private lateinit var shadeViewController: ShadeViewController
+ private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
private lateinit var qsController: QuickSettingsController
- fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+ fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
this.qsController = qsController
- this.shadeViewController = svController
+ this.shadeBackActionInteractor = svController
}
override fun start() {
@@ -114,16 +114,16 @@ constructor(
return true
}
if (qsController.expanded) {
- shadeViewController.animateCollapseQs(false)
+ shadeBackActionInteractor.animateCollapseQs(false)
return true
}
- if (shadeViewController.closeUserSwitcherIfOpen()) {
+ if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
return true
}
if (shouldBackBeHandled()) {
- if (shadeViewController.canBeCollapsed()) {
+ if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
- shadeViewController.onBackPressed()
+ shadeBackActionInteractor.onBackPressed()
shadeController.animateCollapseShade()
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 792a7efeb109..c8fb0449279f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -17,31 +17,20 @@
package com.android.systemui.biometrics.data.repository
import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-import android.os.Handler
import android.util.Size
import android.view.DisplayInfo
-import com.android.app.tracing.traceSection
-import com.android.internal.util.ArrayUtils
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.toDisplayRotation
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import java.util.concurrent.Executor
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
+import com.android.systemui.display.data.repository.DisplayRepository
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -66,52 +55,23 @@ interface DisplayStateRepository {
val currentDisplaySize: StateFlow<Size>
}
-// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
-// instead.
@SysUISingleton
class DisplayStateRepositoryImpl
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Background backgroundScope: CoroutineScope,
@Application val context: Context,
- deviceStateManager: DeviceStateManager,
- displayManager: DisplayManager,
- @Main handler: Handler,
- @Background backgroundExecutor: Executor,
- @Background backgroundDispatcher: CoroutineDispatcher,
+ deviceStateRepository: DeviceStateRepository,
+ displayRepository: DisplayRepository,
) : DisplayStateRepository {
override val isReverseDefaultRotation =
context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
override val isInRearDisplayMode: StateFlow<Boolean> =
- conflatedCallbackFlow {
- val sendRearDisplayStateUpdate = { state: Boolean ->
- trySendWithFailureLogging(
- state,
- TAG,
- "Error sending rear display state update to $state"
- )
- }
-
- val callback =
- DeviceStateManager.DeviceStateCallback { state ->
- val isInRearDisplayMode =
- ArrayUtils.contains(
- context.resources.getIntArray(
- com.android.internal.R.array.config_rearDisplayDeviceStates
- ),
- state
- )
- sendRearDisplayStateUpdate(isInRearDisplayMode)
- }
-
- sendRearDisplayStateUpdate(false)
- deviceStateManager.registerCallback(backgroundExecutor, callback)
- awaitClose { deviceStateManager.unregisterCallback(callback) }
- }
- .flowOn(backgroundDispatcher)
+ deviceStateRepository.state
+ .map { it == REAR_DISPLAY }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
@@ -123,37 +83,10 @@ constructor(
}
private val currentDisplayInfo: StateFlow<DisplayInfo> =
- conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayRemoved(displayId: Int) {}
-
- override fun onDisplayAdded(displayId: Int) {}
-
- override fun onDisplayChanged(displayId: Int) {
- traceSection(
- "DisplayStateRepository" +
- ".currentRotationDisplayListener#onDisplayChanged"
- ) {
- val displayInfo = getDisplayInfo()
- trySendWithFailureLogging(
- displayInfo,
- TAG,
- "Error sending displayInfo to $displayInfo"
- )
- }
- }
- }
- displayManager.registerDisplayListener(
- callback,
- handler,
- EVENT_FLAG_DISPLAY_CHANGED
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .flowOn(backgroundDispatcher)
+ displayRepository.displayChangeEvent
+ .map { getDisplayInfo() }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.Eagerly,
initialValue = getDisplayInfo(),
)
@@ -170,7 +103,7 @@ constructor(
currentDisplayInfo
.map { rotationToDisplayRotation(it.rotation) }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
)
@@ -179,7 +112,7 @@ constructor(
currentDisplayInfo
.map { Size(it.naturalWidth, it.naturalHeight) }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
Size(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index ec29bd6014ef..89cdd25181cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -32,19 +32,13 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
interface StickyKeysRepository {
@@ -53,14 +47,12 @@ interface StickyKeysRepository {
}
@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
private val inputManager: InputManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val secureSettings: SecureSettings,
- userRepository: UserRepository,
+ secureSettingsRepository: UserAwareSecureSettingsRepository,
private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {
@@ -78,25 +70,10 @@ constructor(
.flowOn(backgroundDispatcher)
override val settingEnabled: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { stickyKeySettingObserver(it.id) }
- .flowOn(backgroundDispatcher)
-
- private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
- return secureSettings
- .observerFlow(userId, SETTING_KEY)
- .onStart { emit(Unit) }
- .map { isSettingEnabledForCurrentUser(userId) }
- .distinctUntilChanged()
+ secureSettingsRepository
+ .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
.onEach { stickyKeysLogger.logNewSettingValue(it) }
- }
-
- private fun isSettingEnabledForCurrentUser(userId: Int) =
- secureSettings.getIntForUser(
- /* name= */ SETTING_KEY,
- /* default= */ 0,
- /* userHandle= */ userId
- ) != 0
+ .flowOn(backgroundDispatcher)
private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
val keys = linkedMapOf<ModifierKey, Locked>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a97c152ba7e3..0cf74a1ff32e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,15 +24,15 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample as sampleUtil
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
@SysUISingleton
@@ -62,14 +62,17 @@ constructor(
listenForTransitionToCamera(scope, keyguardInteractor)
}
+ @FlowPreview
private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
// Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
// will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
// happening prematurely.
- .onEach { delay(50) }
- .sample(
+ // This should eventually be removed in favor of
+ // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
+ .sample(150L)
+ .sampleCombine(
keyguardInteractor.primaryBouncerShowing,
startedKeyguardTransitionStep,
powerInteractor.isAwake,
@@ -111,19 +114,20 @@ constructor(
private fun listenForAlternateBouncerToGone() {
scope.launch {
- keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
- (isKeyguardGoingAway, keyguardState) ->
- if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- startTransitionTo(KeyguardState.GONE)
+ keyguardInteractor.isKeyguardGoingAway
+ .sampleUtil(finishedKeyguardState, ::Pair)
+ .collect { (isKeyguardGoingAway, keyguardState) ->
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+ startTransitionTo(KeyguardState.GONE)
+ }
}
- }
}
}
private fun listenForAlternateBouncerToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
+ .sampleUtil(startedKeyguardTransitionStep, ::Pair)
.collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
if (
isPrimaryBouncerShowing &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8fa33ee7d0ca..5606d4301cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,18 +21,18 @@ import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -85,15 +85,16 @@ constructor(
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- ::Pair
- ),
- ::toTriple
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
)
- .collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
+ .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
val modeOnCanceled =
if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
@@ -126,15 +127,29 @@ constructor(
}
private fun listenForAodToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
(biometricUnlockState, keyguardState) ->
+ KeyguardWmStateRefactor.assertInLegacyMode()
if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
startTransitionTo(KeyguardState.GONE)
}
}
}
}
+
+ /**
+ * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
+ * AOD.
+ */
+ fun dismissAod() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7477624f52d8..6b85a634ea62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -68,11 +68,11 @@ constructor(
scope.launch {
keyguardInteractor.isKeyguardShowing
.sample(
- startedKeyguardTransitionStep,
+ currentKeyguardState,
communalInteractor.isIdleOnCommunal,
)
- .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
- if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+ .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && currentState == KeyguardState.GONE) {
val to =
if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 8784723a3909..c496a6ee437f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindReposi
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.util.kotlin.toPx
import dagger.Lazy
import javax.inject.Inject
@@ -44,6 +45,7 @@ constructor(
transitionInteractor: KeyguardTransitionInteractor,
inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
swipeToDismissInteractor: SwipeToDismissInteractor,
+ notificationLaunchInteractor: NotificationLaunchAnimationInteractor,
) {
/**
* The view params to use for the surface. These params describe the alpha/translation values to
@@ -53,10 +55,20 @@ constructor(
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.currentKeyguardState,
- ) { startedStep, currentState ->
+ notificationLaunchInteractor.isLaunchAnimationRunning,
+ ) { startedStep, currentState, notifAnimationRunning ->
// If we're in transition to GONE, special unlock animation params apply.
if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
- if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) {
+ if (notifAnimationRunning) {
+ // If the notification launch animation is running, leave the alpha at 0f.
+ // The ActivityLaunchAnimator will morph it from the notification at the
+ // appropriate time.
+ return@combine KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ } else if (
+ inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()
+ ) {
// The Launcher icons have their own translation/alpha animations during the
// in-window animation. We'll just make the surface visible and let Launcher
// do its thing.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b43ab5e9110d..310f13d49e16 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -60,6 +60,7 @@ constructor(
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
+ private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -346,6 +347,7 @@ constructor(
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ AOD -> fromAodTransitionInteractor.get().dismissAod()
else ->
Log.e(
"KeyguardTransitionInteractor",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 19d00cfea114..c7f262a2ac80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -42,7 +42,7 @@ constructor(
private val lightRevealScrimRepository: LightRevealScrimRepository,
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
- powerInteractor: PowerInteractor,
+ private val powerInteractor: PowerInteractor,
) {
init {
@@ -83,11 +83,13 @@ constructor(
// (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
// correct end states are respected even if the screen turned off (or was still off)
// when the animation finished
- powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
- it == 1f ||
- it == 0f
+ screenIsShowingContent() || it == 1f || it == 0f
}
+ private fun screenIsShowingContent() =
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 5c2df4581ff0..3ccbdba6d58e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -60,6 +60,7 @@ sealed class TransitionInteractor(
// The following are MutableSharedFlows, and do not require flowOn
val startedKeyguardState = transitionInteractor.startedKeyguardState
val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+ val currentKeyguardState = transitionInteractor.currentKeyguardState
suspend fun startTransitionTo(
toState: KeyguardState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 49af66406296..b81793ecec64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -26,7 +28,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@@ -37,6 +38,7 @@ constructor(
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
) {
private val defaultSurfaceBehindVisibility =
transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -72,8 +74,7 @@ constructor(
*/
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
- transitionInteractor
- .isInTransitionToAnyState
+ transitionInteractor.isInTransitionToAnyState
.flatMapLatest { isInTransition ->
if (!isInTransition) {
defaultSurfaceBehindVisibility
@@ -99,12 +100,16 @@ constructor(
combine(
transitionInteractor.isInTransitionToState(KeyguardState.GONE),
transitionInteractor.finishedKeyguardState,
- surfaceBehindInteractor.isAnimatingSurface
- ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ surfaceBehindInteractor.isAnimatingSurface,
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+ // Using the animation if we're animating it directly, or if the
+ // ActivityLaunchAnimator is in the process of animating it.
+ val animationsRunning = isAnimatingSurface || notifLaunchRunning
// We may still be animating the surface after the keyguard is fully GONE, since
// some animations (like the translation spring) are not tied directly to the
// transition step amount.
- isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 3737e6fdf13e..d26356ebc92b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -46,6 +47,14 @@ constructor(
to = KeyguardState.GONE,
)
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ )
+
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 780e323a96bc..828e03301b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -28,6 +28,9 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import javax.inject.Inject
@@ -117,7 +120,10 @@ constructor(
): Flow<BurnInModel> {
return combine(
merge(
- keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
+ keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
+ it.value
+ },
keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
)
.map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 709e184e6e52..f8a12bd226ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -61,6 +61,9 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
@@ -92,10 +95,15 @@ constructor(
val alpha: Flow<Float> =
combine(
communalInteractor.isIdleOnCommunal,
+ // The transitions are mutually exclusive, so they are safe to merge to get the last
+ // value emitted by any of them. Do not add flows that cannot make this guarantee.
merge(
aodAlphaViewModel.alpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
)
) { isIdleOnCommunal, alpha ->
if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index a26ef0755123..d981650adf60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -46,12 +46,14 @@ constructor(
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
+ duration = 200.milliseconds,
onStep = { 1 - it },
onFinish = { 0f },
onCancel = { 1f },
)
+ val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 3f4f34745c4b..8d918e76b1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -22,9 +22,12 @@ import android.text.format.DateUtils
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.res.R
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
+
+private const val TAG = "SeekBarObserver"
/**
* Observer for changes from SeekBarViewModel.
@@ -39,6 +42,10 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
}
+ // Trace state loggers for playing and listening states of progress bar.
+ private val playingStateLogger = TraceStateLogger("$TAG#playing")
+ private val listeningStateLogger = TraceStateLogger("$TAG#listening")
+
val seekBarEnabledMaxHeight =
holder.seekBar.context.resources.getDimensionPixelSize(
R.dimen.qs_media_enabled_seekbar_height
@@ -103,9 +110,13 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
return
}
+ playingStateLogger.log("${data.playing}")
+ listeningStateLogger.log("${data.listening}")
+
holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
holder.seekBar.isEnabled = data.seekAvailable
- progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
+ progressDrawable?.animate =
+ data.playing && !data.scrubbing && animationEnabled && data.listening
progressDrawable?.transitionEnabled = !data.seekAvailable
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index a91917ab97c2..40a9b9c699bc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -84,7 +84,16 @@ constructor(
@Background private val bgExecutor: RepeatableExecutor,
private val falsingManager: FalsingManager,
) {
- private var _data = Progress(false, false, false, false, null, 0)
+ private var _data =
+ Progress(
+ enabled = false,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = null,
+ duration = 0,
+ listening = false
+ )
set(value) {
val enabledChanged = value.enabled != field.enabled
field = value
@@ -239,7 +248,7 @@ constructor(
)
false
else true
- _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
+ _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
checkIfPollingNeeded()
}
@@ -258,6 +267,7 @@ constructor(
scrubbing = false,
elapsedTime = position,
duration = 100,
+ listening = false,
)
}
@@ -548,9 +558,12 @@ constructor(
data class Progress(
val enabled: Boolean,
val seekAvailable: Boolean,
+ /** whether playback state is not paused or connecting */
val playing: Boolean,
val scrubbing: Boolean,
val elapsedTime: Int?,
- val duration: Int
+ val duration: Int,
+ /** whether seekBar is listening to progress updates */
+ val listening: Boolean,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 02f0d12c7069..038582c4e999 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -26,14 +26,15 @@ import android.view.ViewOutlineProvider
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
+import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.animation.PhysicsAnimator
@@ -42,6 +43,7 @@ private const val DISMISS_DELAY = 100L
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
+private const val TAG = "MediaCarouselScrollHandler"
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio were not
@@ -63,6 +65,9 @@ class MediaCarouselScrollHandler(
private val logSmartspaceImpression: (Boolean) -> Unit,
private val logger: MediaUiEventLogger
) {
+ /** Trace state logger for media carousel visibility */
+ private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
+
/** Is the view in RTL */
val isRtl: Boolean
get() = scrollView.isLayoutRtl
@@ -182,6 +187,7 @@ class MediaCarouselScrollHandler(
if (field != value) {
field = value
seekBarUpdateListener.invoke(field)
+ visibleStateLogger.log("$visibleToUser")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7512e2..afe628527833 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
package com.android.systemui.mediaprojection
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
import android.content.Context
import android.media.projection.IMediaProjection
import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@ import android.util.Log
*/
class MediaProjectionServiceHelper {
companion object {
+ /**
+ * This change id ensures that users are presented with a choice of capturing a single app
+ * or the entire screen when initiating a MediaProjection session, overriding the usage of
+ * MediaProjectionConfig#createConfigForDefaultDisplay.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
private const val TAG = "MediaProjectionServiceHelper"
private val service =
IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b293dcb..0769731bffb1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
package com.android.systemui.mediaprojection.permission;
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -104,6 +109,7 @@ public class MediaProjectionPermissionActivity extends Activity
}
@Override
+ @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -231,6 +237,9 @@ public class MediaProjectionPermissionActivity extends Activity
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
+ final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -242,6 +251,7 @@ public class MediaProjectionPermissionActivity extends Activity
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
+ overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e934f3cf..9ce8070131fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@ class MediaProjectionPermissionDialogDelegate(
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig),
+ createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@ class MediaProjectionPermissionDialogDelegate(
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean = false,
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -80,8 +82,13 @@ class MediaProjectionPermissionDialogDelegate(
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
+ // The single app option should only be disabled if there is an app name provided,
+ // the client has setup a MediaProjection with
+ // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+ // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
appName != null &&
+ !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 1b3b5848a7ce..58e761370457 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -40,7 +40,7 @@ constructor(
val icon =
Icon.Loaded(
resources.getDrawable(
- if (data.isEnabled) {
+ if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
R.drawable.qs_flashlight_icon_on
} else {
R.drawable.qs_flashlight_icon_off
@@ -51,17 +51,22 @@ constructor(
)
this.icon = { icon }
- if (data.isEnabled) {
+ contentDescription = label
+
+ if (data is FlashlightTileModel.FlashlightTemporarilyUnavailable) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.quick_settings_flashlight_camera_in_use)
+ stateDescription = secondaryLabel
+ supportedActions = setOf()
+ return@build
+ } else if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
} else {
activationState = QSTileState.ActivationState.INACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
}
- contentDescription = label
- supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- )
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 53d4cf96809c..1544804c3291 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -38,25 +38,30 @@ constructor(
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<FlashlightTileModel> = conflatedCallbackFlow {
- val initialValue = flashlightController.isEnabled
- trySend(FlashlightTileModel(initialValue))
-
val callback =
object : FlashlightController.FlashlightListener {
override fun onFlashlightChanged(enabled: Boolean) {
- trySend(FlashlightTileModel(enabled))
+ trySend(FlashlightTileModel.FlashlightAvailable(enabled))
}
override fun onFlashlightError() {
- trySend(FlashlightTileModel(false))
+ trySend(FlashlightTileModel.FlashlightAvailable(false))
}
override fun onFlashlightAvailabilityChanged(available: Boolean) {
- trySend(FlashlightTileModel(flashlightController.isEnabled))
+ trySend(
+ if (available)
+ FlashlightTileModel.FlashlightAvailable(flashlightController.isEnabled)
+ else FlashlightTileModel.FlashlightTemporarilyUnavailable
+ )
}
}
flashlightController.addCallback(callback)
awaitClose { flashlightController.removeCallback(callback) }
}
+ /**
+ * Used to determine if the tile should be displayed. Not to be confused with the availability
+ * in the data model above. This flow signals whether the tile should be shown or hidden.
+ */
override fun availability(user: UserHandle): Flow<Boolean> =
flowOf(flashlightController.hasFlashlight())
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
index 9180e3080d07..bedd65e0072f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -35,7 +35,10 @@ constructor(
with(input) {
when (action) {
is QSTileUserAction.Click -> {
- if (!ActivityManager.isUserAMonkey()) {
+ if (
+ !ActivityManager.isUserAMonkey() &&
+ input.data is FlashlightTileModel.FlashlightAvailable
+ ) {
flashlightController.setFlashlight(!input.data.isEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index ef6b2be06747..f54b371e6e22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -16,9 +16,14 @@
package com.android.systemui.qs.tiles.impl.flashlight.domain.model
-/**
- * Flashlight tile model.
- *
- * @param isEnabled is true when the falshlight is enabled;
- */
-@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
+sealed interface FlashlightTileModel {
+ /**
+ * In this state, the tile can be turned on or off.
+ *
+ * @param isEnabled is true when the flashlight is on and false when off.
+ */
+ @JvmInline value class FlashlightAvailable(val isEnabled: Boolean) : FlashlightTileModel
+
+ /** In this state the tile is grayed out and flashlight cannot be turned on. */
+ data object FlashlightTemporarilyUnavailable : FlashlightTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 56c0ca910bd7..dcd87c0fe845 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.startable
+import android.app.StatusBarManager
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -42,6 +43,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
@@ -85,6 +87,7 @@ constructor(
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ private val centralSurfaces: CentralSurfaces,
) : CoreStartable {
override fun start() {
@@ -95,6 +98,7 @@ constructor(
hydrateSystemUiState()
collectFalsingSignals()
hydrateWindowFocus()
+ hydrateInteractionState()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -376,6 +380,46 @@ constructor(
}
}
+ /** Keeps the interaction state of [CentralSurfaces] up-to-date. */
+ private fun hydrateInteractionState() {
+ applicationScope.launch {
+ deviceEntryInteractor.isUnlocked
+ .map { !it }
+ .flatMapLatest { isDeviceLocked ->
+ if (isDeviceLocked) {
+ sceneInteractor.transitionState
+ .mapNotNull { it as? ObservableTransitionState.Idle }
+ .map { it.scene }
+ .distinctUntilChanged()
+ .map { sceneKey ->
+ when (sceneKey) {
+ // When locked, showing the lockscreen scene should be reported
+ // as "interacting" while showing other scenes should report as
+ // "not interacting".
+ //
+ // This is done here in order to match the legacy
+ // implementation. The real reason why is lost to lore and myth.
+ SceneKey.Lockscreen -> true
+ SceneKey.Bouncer -> false
+ SceneKey.Shade -> false
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+ .collect { isInteractingOrNull ->
+ isInteractingOrNull?.let { isInteracting ->
+ centralSurfaces.setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ isInteracting,
+ )
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index be1fa2bcadf9..e9af295d1a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,11 +30,11 @@ import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
@@ -236,7 +236,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mTracking = true;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), false);
SeekableSliderEventProducer eventProducer =
@@ -255,7 +255,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mTracking = false;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), true);
SeekableSliderEventProducer eventProducer =
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
index 3a30880eeb5c..a8641bfac1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -21,10 +21,10 @@ import com.android.internal.logging.UiEventLogger;
public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "slider started to track touch")
- SLIDER_STARTED_TRACKING_TOUCH(1472),
- @UiEvent(doc = "slider stopped tracking touch")
- SLIDER_STOPPED_TRACKING_TOUCH(1473);
+ @UiEvent(doc = "brightness slider started to track touch")
+ BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472),
+ @UiEvent(doc = "brightness slider stopped tracking touch")
+ BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e219bccfecfa..133fa12f8a87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1021,22 +1021,24 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
instantCollapse();
} else {
mView.animate().cancel();
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- // Translate up by 4%.
- .translationY(mView.getHeight() * -0.04f)
- // This start delay is to give us time to animate out before
- // the launcher icons animation starts, so use that as our
- // duration.
- .setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_ACCELERATE)
- .withEndAction(() -> {
- instantCollapse();
- mView.setAlpha(1f);
- mView.setTranslationY(0f);
- })
- .start();
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ // Translate up by 4%.
+ .translationY(mView.getHeight() * -0.04f)
+ // This start delay is to give us time to animate out before
+ // the launcher icons animation starts, so use that as our
+ // duration.
+ .setDuration(unlockAnimationStartDelay)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
+ .withEndAction(() -> {
+ instantCollapse();
+ mView.setAlpha(1f);
+ mView.setTranslationY(0f);
+ })
+ .start();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 67bb8144af96..f89a9c701047 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@ import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
import dagger.Binds
@@ -35,6 +36,10 @@ abstract class ShadeEmptyImplModule {
@Binds
@SysUISingleton
+ abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor
+
+ @Binds
+ @SysUISingleton
abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index fc2c3ee14206..e4d5d22c602c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -24,6 +24,8 @@ import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@ abstract class ShadeModule {
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideShadeBackActionInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
+ sceneContainerOff: Provider<NotificationPanelViewController>
+ ): ShadeBackActionInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 4f970b3923aa..0befb61ff814 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager
* this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
* pulled up into ShadeViewController.
*/
-interface ShadeSurface : ShadeViewController {
+interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
/** Initialize objects instead of injecting to avoid circular dependencies. */
fun initDependencies(
centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3430eedd4ed6..74035bd442db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -79,15 +79,8 @@ interface ShadeViewController {
/** Collapses the shade instantly without animation. */
fun instantCollapse()
- /**
- * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
- * in split shade, it will collapse the whole shade.
- *
- * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
- */
- fun animateCollapseQs(fullyCollapse: Boolean)
-
/** Returns whether the shade can be collapsed. */
+ @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
fun canBeCollapsed(): Boolean
/** Returns whether the shade is in the process of collapsing. */
@@ -142,22 +135,6 @@ interface ShadeViewController {
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
- /**
- * Close the keyguard user switcher if it is open and capable of closing.
- *
- * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
- * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
- *
- * @return true if the keyguard user switcher was open, and is now closed
- */
- fun closeUserSwitcherIfOpen(): Boolean
-
- /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
- fun onBackPressed()
-
- /** Sets progress of the predictive back animation. */
- fun onBackProgressed(progressFraction: Float)
-
/** Sets the alpha value of the shade to a value between 0 and 255. */
fun setAlpha(alpha: Int, animate: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 1240c6e3262b..5d966ac51bc8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,13 +19,15 @@ package com.android.systemui.shade
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
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 {
+class ShadeViewControllerEmptyImpl @Inject constructor() :
+ ShadeViewController, ShadeBackActionInteractor {
override fun expand(animate: Boolean) {}
override fun expandToQs() {}
override fun expandToNotifications() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
new file mode 100644
index 000000000000..15ea2197e5ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+/** Allows the back action to interact with the shade. */
+interface ShadeBackActionInteractor {
+ /**
+ * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+ * in split shade, it will collapse the whole shade.
+ *
+ * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ fun animateCollapseQs(fullyCollapse: Boolean)
+
+ /** Returns whether the shade can be collapsed. */
+ fun canBeCollapsed(): Boolean
+
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+ * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ fun closeUserSwitcherIfOpen(): Boolean
+
+ /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ fun onBackPressed()
+
+ /** Sets progress of the predictive back animation. */
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ fun onBackProgressed(progressFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
new file mode 100644
index 000000000000..9bbe1bd28fee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Implementation of ShadeBackActionInteractor backed by scenes. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeBackActionInteractorImpl
+@Inject
+constructor(
+ val shadeInteractor: ShadeInteractor,
+ val sceneInteractor: SceneInteractor,
+ val deviceEntryInteractor: DeviceEntryInteractor,
+) : ShadeBackActionInteractor {
+ override fun animateCollapseQs(fullyCollapse: Boolean) {
+ if (shadeInteractor.isQsExpanded.value) {
+ val key =
+ if (fullyCollapse) {
+ if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ } else {
+ SceneKey.Shade
+ }
+ sceneInteractor.changeScene(SceneModel(key), "animateCollapseQs")
+ }
+ }
+
+ override fun canBeCollapsed(): Boolean {
+ return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
+ }
+
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+
+ override fun onBackPressed() {
+ animateCollapseQs(false)
+ }
+
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ override fun onBackProgressed(progressFraction: Float) {
+ // Not supported. Do nothing.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
index 52a1c1555591..095d30ef55df 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -21,9 +21,7 @@ import android.os.Parcelable
import android.widget.RemoteViews
import com.android.systemui.communal.smartspace.CommunalSmartspaceController
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,7 +41,6 @@ class SmartspaceRepositoryImpl
@Inject
constructor(
private val communalSmartspaceController: CommunalSmartspaceController,
- @Main private val uiExecutor: Executor,
) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
override val isSmartspaceRemoteViewsEnabled: Boolean
@@ -54,18 +51,12 @@ constructor(
override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
_communalSmartspaceTargets
.onStart {
- uiExecutor.execute {
- communalSmartspaceController.addListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
+ communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
}
.onCompletion {
- uiExecutor.execute {
- communalSmartspaceController.removeListener(
- listener = this@SmartspaceRepositoryImpl
- )
- }
+ communalSmartspaceController.removeListener(
+ listener = this@SmartspaceRepositoryImpl
+ )
}
override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 3f2c818399d1..7c718645bc74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -386,6 +386,7 @@ constructor(
}
}
+ @Deprecated("As part of b/301915812")
private fun scheduleDelayedDozeAmountAnimation() {
val alreadyRunning = delayedDozeAmountAnimator != null
logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 3915c3763a41..811da51b55ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -31,20 +32,24 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -70,6 +75,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.isActive
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
+@SysUISingleton
class SharedNotificationContainerViewModel
@Inject
constructor(
@@ -80,6 +86,9 @@ constructor(
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -94,6 +103,12 @@ constructor(
mapOf<Edge?, Flow<Float>>(
Edge(from = LOCKSCREEN, to = DREAMING) to
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = GONE) to
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = ALTERNATE_BOUNCER, to = GONE) to
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = PRIMARY_BOUNCER, to = GONE) to
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
Edge(from = DREAMING, to = LOCKSCREEN) to
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
Edge(from = LOCKSCREEN, to = OCCLUDED) to
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 209936108f6b..3e7089c70deb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,6 +28,7 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -2497,7 +2498,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- if (shouldAnimateDozeWakeup()) {
+ boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled();
+ if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
// If this is false, the power button must be physically pressed in order to
// trigger fingerprint authentication.
final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69282ae5f9eb..3ad60d946c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1442,10 +1442,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
-
- if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition();
- }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 3741f143dd8d..c0e36b2ab42a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -91,7 +91,9 @@ public interface StatusBarFragmentModule {
@StatusBarFragmentScope
@Named(OPERATOR_NAME_VIEW)
static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
- return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ operatorName.setVisibility(View.GONE);
+ return operatorName;
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index f36c335e0f44..d509b2da482e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,6 +16,9 @@
package com.android.systemui.util.settings;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
+
import dagger.Binds;
import dagger.Module;
@@ -36,4 +39,9 @@ public interface SettingsUtilModule {
/** Bind GlobalSettingsImpl to GlobalSettings. */
@Binds
GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
+
+ /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
+ @Binds
+ UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
+ UserAwareSecureSettingsRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
new file mode 100644
index 000000000000..d3e50803b5d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+interface UserAwareSecureSettingsRepository {
+
+ /**
+ * Emits boolean value of the setting for active user. Also emits starting value when
+ * subscribed.
+ * See: [SettingsProxy.getBool].
+ */
+ fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
+}
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val userRepository: UserRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : UserAwareSecureSettingsRepository {
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
+ return secureSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 0ba9abe2d36c..f490f3c56987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -45,7 +45,7 @@ import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -89,7 +89,7 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var qsController: QuickSettingsController
- @Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var windowRootView: WindowRootView
@Mock private lateinit var viewRootImpl: ViewRootImpl
@@ -123,7 +123,7 @@ class BackActionInteractorTest : SysuiTestCase() {
notificationShadeWindowController,
windowRootViewVisibilityInteractor
)
- .apply { this.setup(qsController, shadeViewController) }
+ .apply { this.setup(qsController, shadeBackActionInteractor) }
}
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -165,19 +165,19 @@ class BackActionInteractorTest : SysuiTestCase() {
val result = backActionInteractor.onBackRequested()
assertTrue(result)
- verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
verify(statusBarKeyguardViewManager, never()).onBackPressed()
}
@Test
fun testOnBackRequested_closeUserSwitcherIfOpen() {
- whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+ whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
val result = backActionInteractor.onBackRequested()
assertTrue(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -189,7 +189,7 @@ class BackActionInteractorTest : SysuiTestCase() {
assertFalse(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -290,11 +290,11 @@ class BackActionInteractorTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
callback.onBackProgressed(createBackEvent(0.3f))
- verify(shadeViewController, never()).onBackProgressed(0.3f)
+ verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
}
@Test
@@ -305,11 +305,11 @@ class BackActionInteractorTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
callback.onBackProgressed(createBackEvent(0.4f))
- verify(shadeViewController).onBackProgressed(0.4f)
+ verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
private fun getBackInvokedCallback(): OnBackInvokedCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index a84778a49643..eae953e2031e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -16,11 +16,9 @@
package com.android.systemui.keyguard.data.repository
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.os.Handler
import android.util.Size
import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayInfo
import android.view.Surface
import androidx.test.filters.SmallTest
@@ -29,61 +27,37 @@ import com.android.systemui.biometrics.data.repository.DisplayStateRepository
import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
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.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
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.ArgumentCaptor
-import org.mockito.ArgumentMatchers.same
-import org.mockito.Captor
-import org.mockito.Mock
import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
-private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class DisplayStateRepositoryTest : SysuiTestCase() {
- @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var deviceStateManager: DeviceStateManager
- @Mock private lateinit var displayManager: DisplayManager
- @Mock private lateinit var handler: Handler
- @Mock private lateinit var display: Display
- private lateinit var underTest: DisplayStateRepository
-
+ private val display = mock<Display>()
private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ private val fakeDeviceStateRepository = FakeDeviceStateRepository()
+ private val fakeDisplayRepository = FakeDisplayRepository()
- @Captor
- private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener>
+ private lateinit var underTest: DisplayStateRepository
@Before
fun setUp() {
- val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.array.config_rearDisplayDeviceStates,
- rearDisplayDeviceStates
- )
-
mContext.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_reverseDefaultRotation,
false
@@ -96,11 +70,8 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
DisplayStateRepositoryImpl(
testScope.backgroundScope,
mContext,
- deviceStateManager,
- displayManager,
- handler,
- fakeExecutor,
- UnconfinedTestDispatcher(),
+ fakeDeviceStateRepository,
+ fakeDisplayRepository,
)
}
@@ -110,12 +81,10 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
runCurrent()
- val callback = deviceStateManager.captureCallback()
-
- callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+ fakeDeviceStateRepository.emit(DeviceState.FOLDED)
assertThat(isInRearDisplayMode).isFalse()
- callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+ fakeDeviceStateRepository.emit(DeviceState.REAR_DISPLAY)
assertThat(isInRearDisplayMode).isTrue()
}
@@ -125,19 +94,13 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
val currentRotation by collectLastValue(underTest.currentRotation)
runCurrent()
- verify(displayManager)
- .registerDisplayListener(
- displayListenerCaptor.capture(),
- same(handler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
- )
-
whenever(display.getDisplayInfo(any())).then {
val info = it.getArgument<DisplayInfo>(0)
info.rotation = Surface.ROTATION_90
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90)
+
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
whenever(display.getDisplayInfo(any())).then {
@@ -145,7 +108,8 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
info.rotation = Surface.ROTATION_180
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
}
@@ -155,13 +119,6 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
val currentSize by collectLastValue(underTest.currentDisplaySize)
runCurrent()
- verify(displayManager)
- .registerDisplayListener(
- displayListenerCaptor.capture(),
- same(handler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
- )
-
whenever(display.getDisplayInfo(any())).then {
val info = it.getArgument<DisplayInfo>(0)
info.rotation = Surface.ROTATION_0
@@ -169,7 +126,7 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
info.logicalHeight = 200
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentSize).isEqualTo(Size(100, 200))
whenever(display.getDisplayInfo(any())).then {
@@ -179,12 +136,7 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
info.logicalHeight = 200
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentSize).isEqualTo(Size(200, 100))
}
}
-
-private fun DeviceStateManager.captureCallback() =
- withArgCaptor<DeviceStateManager.DeviceStateCallback> {
- verify(this@captureCallback).registerCallback(any(), capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 6eebb6d19e5e..d14d72d90a31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -37,6 +37,7 @@ 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.settings.FakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -68,11 +69,15 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
@Before
fun setup() {
+ val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher
+ )
val stickyKeysRepository = StickyKeysRepositoryImpl(
inputManager,
dispatcher,
- secureSettings,
- userRepository,
+ settingsRepository,
mock<StickyKeysLogger>()
)
setStickyKeySetting(enabled = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
new file mode 100644
index 000000000000..c174cb87d4de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import 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.Mockito.reset
+import org.mockito.Mockito.spy
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromGoneTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromGoneTransitionInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
+ @Test
+ fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ ),
+ ),
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ fun testTransitionsToLockscreen_ifFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 668fb644065d..6d8e7aa0703c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,17 +27,15 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.FlingInfo
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -132,13 +130,11 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
)
runCurrent()
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- .also {
- assertEquals(KeyguardState.LOCKSCREEN, it.from)
- assertEquals(KeyguardState.GONE, it.to)
- }
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
}
@Test
@@ -155,6 +151,6 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
)
runCurrent()
- verify(transitionRepository, never()).startTransition(any())
+ assertThatRepository(transitionRepository).noTransitionsStarted()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index f23dd557fc1f..3f05bfae6777 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.mockTopActivityClassName
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.assertValuesMatch
import com.google.common.truth.Truth.assertThat
@@ -192,4 +193,38 @@ class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
)
.inOrder()
}
+
+ @Test
+ fun testSurfaceBehindModel_fromNotificationLaunch() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+ runCurrent()
+
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ )
+ runCurrent()
+
+ values.assertValuesMatch(
+ // We should be at alpha = 0f during the animation.
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bb61d18f260f..5b93df5a0bad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -37,9 +37,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -51,13 +51,12 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -254,15 +253,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -281,15 +278,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -308,15 +303,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -336,17 +329,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -367,17 +358,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the device begins to dream and the dream is lockscreen hosted
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -396,15 +385,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -423,15 +410,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -454,17 +439,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the lockscreen hosted dream stops
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Lockscreen should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -484,15 +467,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Gone should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -514,15 +495,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -547,15 +526,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -579,15 +556,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.OCCLUDED,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -603,15 +578,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -649,7 +622,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN a signal comes that dreaming is enabled
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
// THEN the transition is ignored
verify(transitionRepository, never()).startTransition(any())
@@ -667,15 +640,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -699,15 +670,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.DOZING,
+ ownerName = FromDozingTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -726,15 +695,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -753,15 +720,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -776,15 +741,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -804,17 +767,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -835,17 +796,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the device begins to dream with the lockscreen hosted dream
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -868,15 +827,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.GONE,
+ ownerName = FromGoneTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -894,21 +851,19 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@Test
- fun alternateBoucnerToAod() =
+ fun alternateBouncerToAod() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
bouncerRepository.setAlternateVisible(true)
@@ -924,17 +879,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(200L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -957,17 +910,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(200L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -987,17 +938,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1025,18 +974,16 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1056,15 +1003,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1084,15 +1030,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1108,15 +1053,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1140,16 +1084,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1171,15 +1113,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1202,15 +1143,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GONE should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1231,15 +1171,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1268,15 +1207,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GLANCEABLE_HUB should occur
- assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromOccludedTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1293,15 +1231,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1318,15 +1255,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1344,15 +1280,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1369,15 +1304,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDozingTransitionInteractor",
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1396,15 +1330,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1426,15 +1359,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1450,15 +1382,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1474,15 +1405,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1498,15 +1428,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1532,14 +1461,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
runCurrent()
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1559,14 +1487,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNull() // dragging should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNull() }, // dragging should be manually animated
+ )
// WHEN the user stops dragging and shade is back to expanded
clearInvocations(transitionRepository)
@@ -1575,14 +1502,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(1f)
runCurrent()
- // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.animator).isNotNull()
+ // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1614,15 +1540,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
runCurrent()
// THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub opening is cancelled
clearInvocations(transitionRepository)
@@ -1634,14 +1558,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ )
coroutineContext.cancelChildren()
}
@@ -1672,16 +1595,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
progress.value = .1f
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub closing is cancelled
clearInvocations(transitionRepository)
@@ -1693,14 +1613,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
coroutineContext.cancelChildren()
}
@@ -1715,16 +1632,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1739,16 +1653,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1763,16 +1674,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1796,16 +1704,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1820,16 +1725,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setKeyguardGoingAway(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1849,33 +1751,19 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ advanceTimeBy(100L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
- private fun createKeyguardInteractor(): KeyguardInteractor {
- return KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- commandQueue = commandQueue,
- bouncerRepository = bouncerRepository,
- powerInteractor = powerInteractor,
- )
- .keyguardInteractor
- }
-
private suspend fun TestScope.runTransitionAndSetWakefulness(
from: KeyguardState,
to: KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
new file mode 100644
index 000000000000..655a5510593a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.util
+
+import androidx.core.animation.ValueAnimator
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import junit.framework.Assert.assertEquals
+import kotlin.test.fail
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */
+class KeyguardTransitionRepositorySpySubject
+private constructor(
+ failureMetadata: FailureMetadata,
+ private val repository: KeyguardTransitionRepository,
+) : Subject(failureMetadata, repository) {
+
+ /**
+ * Asserts that we started a transition to the given state, optionally checking additional
+ * parameters. If an animator param or assertion is not provided, we will not assert anything
+ * about the animator.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, {}, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animator: ValueAnimator?,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animatorAssertion: (Subject) -> Unit,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) }
+ .also { transitionInfo ->
+ assertEquals(to, transitionInfo.to)
+ animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator))
+ from?.let { assertEquals(it, transitionInfo.from) }
+ ownerName?.let { assertEquals(it, transitionInfo.ownerName) }
+ modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) }
+ }
+ }
+
+ /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */
+ fun noTransitionsStarted() {
+ verify(repository, never()).startTransition(any())
+ }
+
+ companion object {
+ fun assertThat(
+ repository: KeyguardTransitionRepository
+ ): KeyguardTransitionRepositorySpySubject =
+ assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+ if (!Mockito.mockingDetails(repository).isSpy) {
+ fail(
+ "Cannot assert on a non-spy KeyguardTransitionRepository. " +
+ "Use Mockito.spy(keyguardTransitionRepository)."
+ )
+ }
+ KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+ }
+ .that(repository)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 043dae608aa7..100e579f1d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -24,9 +24,9 @@ import android.view.View
import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -86,7 +86,7 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarGone() {
// WHEN seek bar is disabled
val isEnabled = false
- val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0)
+ val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
observer.onChanged(data)
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
@@ -99,7 +99,7 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarVisible() {
// WHEN seek bar is enabled
val isEnabled = true
- val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
observer.onChanged(data)
// THEN seek bar is visible and thick
assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -109,7 +109,7 @@ class SeekBarObserverTest : SysuiTestCase() {
@Test
fun seekBarProgress() {
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
observer.onChanged(data)
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
@@ -123,7 +123,8 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarDisabledWhenSeekNotAvailable() {
// WHEN seek is not available
val isSeekAvailable = false
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isFalse()
@@ -133,7 +134,8 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarEnabledWhenSeekNotAvailable() {
// WHEN seek is available
val isSeekAvailable = true
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isTrue()
@@ -144,7 +146,7 @@ class SeekBarObserverTest : SysuiTestCase() {
// WHEN playing
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is animating
verify(mockSquigglyProgress).animate = true
@@ -155,7 +157,7 @@ class SeekBarObserverTest : SysuiTestCase() {
// WHEN not playing & not scrubbing
val isPlaying = false
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -166,7 +168,7 @@ class SeekBarObserverTest : SysuiTestCase() {
// WHEN playing & scrubbing
val isPlaying = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -177,7 +179,7 @@ class SeekBarObserverTest : SysuiTestCase() {
// WHEN playing & scrubbing
val isPlaying = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -187,7 +189,7 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
val isEnabled = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -199,7 +201,7 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
val isEnabled = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -211,7 +213,7 @@ class SeekBarObserverTest : SysuiTestCase() {
fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
val isEnabled = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -221,8 +223,8 @@ class SeekBarObserverTest : SysuiTestCase() {
@Test
fun seekBarJumpAnimation() {
- val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000)
- val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000)
+ val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
+ val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
// Set initial position of progress bar
observer.onChanged(data0)
@@ -241,7 +243,7 @@ class SeekBarObserverTest : SysuiTestCase() {
observer.animationEnabled = false
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable does not animate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 000000000000..e044eeca8303
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var dialog: AlertDialog
+
+ private val flags = mock<FeatureFlagsClassic>()
+ private val onStartRecordingClicked = mock<Runnable>()
+ private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+ private val mediaProjectionConfig: MediaProjectionConfig =
+ MediaProjectionConfig.createConfigForDefaultDisplay()
+ private val appName: String = "testApp"
+ private val hostUid: Int = 12345
+
+ private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+ private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleAppDisabled =
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+ @Before
+ fun setUp() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareFalse() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = false
+ val overrideDisableSingleAppOption = false
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareTrue() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = true
+ val overrideDisableSingleAppOption = true
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text1)
+ ?.text
+
+ // check that the first option is single app and enabled
+ assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+ // check that the second option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), secondOptionText)
+ }
+
+ private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+ val delegate =
+ MediaProjectionPermissionDialogDelegate(
+ context,
+ mediaProjectionConfig,
+ {},
+ onStartRecordingClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ hostUid,
+ mediaProjectionMetricsLogger
+ )
+
+ dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+ SystemUIDialog.applyFlags(dialog)
+ SystemUIDialog.setDialogSize(dialog)
+
+ dialog.window?.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ )
+
+ delegate.onCreate(dialog, savedInstanceState = null)
+ dialog.show()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 707a2971b1eb..d757d7144276 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -202,7 +202,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
@Test
fun testSeekBarTrackingStarted() {
whenever(brightnessSliderView.value).thenReturn(42)
- val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
@@ -220,7 +220,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
@Test
fun testSeekBarTrackingStopped() {
whenever(brightnessSliderView.value).thenReturn(23)
- val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c772ee288f8b..8a22f4cf3fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,7 +63,6 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -197,16 +196,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
() -> sceneInteractor);
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 72c52ec17934..f582402b95a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -41,7 +41,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -50,7 +49,6 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -223,18 +221,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
-
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8fd9c808f6d1..fb105e2ef759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,9 +35,9 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -133,14 +133,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
shadeRepository,
{ kosmos.sceneInteractor },
)
- val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- testScope.backgroundScope,
- keyguardTransitionRepository,
- { keyguardInteractor },
- { fromLockscreenTransitionInteractor },
- { fromPrimaryBouncerTransitionInteractor }
- )
+ val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
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
index 32c727c70172..ff882b19ab13 100644
--- 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
@@ -69,7 +69,10 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ }
}
init {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index ed80a869dda8..913759f77013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -1,16 +1,28 @@
-package com.android.systemui.keyboard.stickykeys.data.repository
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -26,26 +38,24 @@ import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
-class StickyKeysRepositoryImplTest : SysuiTestCase() {
+class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
private val secureSettings = FakeSettings()
private val userRepository = Kosmos().fakeUserRepository
- private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
+ private lateinit var repository: UserAwareSecureSettingsRepository
@Before
fun setup() {
- stickyKeysRepository = StickyKeysRepositoryImpl(
- mock<InputManager>(),
- dispatcher,
+ repository = UserAwareSecureSettingsRepositoryImpl(
secureSettings,
userRepository,
- mock<StickyKeysLogger>()
+ dispatcher,
)
userRepository.setUserInfos(USER_INFOS)
- setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+ setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+ setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
}
@Test
@@ -53,7 +63,7 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
assertThat(enabled).isTrue()
}
@@ -63,10 +73,10 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() {
fun settingEnabledEmitsNewValueWhenSettingChanges() {
testScope.runTest {
userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectValues(stickyKeysRepository.settingEnabled)
+ val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
runCurrent()
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+ setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
assertThat(enabled).containsExactly(true, false).inOrder()
}
@@ -76,7 +86,7 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() {
fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
testScope.runTest {
userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
runCurrent()
userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
@@ -85,12 +95,12 @@ class StickyKeysRepositoryImplTest : SysuiTestCase() {
}
}
- private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
- val newValue = if (enabled) "1" else "0"
- secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
+ private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
+ secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
}
private companion object {
+ const val SETTING_NAME = "SETTING_NAME"
val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 1d428c89333e..d45a9a944da2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -95,11 +95,9 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -107,7 +105,6 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -428,17 +425,8 @@ public class BubblesTest extends SysuiTestCase {
shadeRepository,
() -> sceneInteractor);
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..da5cd679351f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.fromAodTransitionInteractor by
+ Kosmos.Fixture {
+ FromAodTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
new file mode 100644
index 000000000000..25fc67a9691b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.fromGoneTransitionInteractor by
+ Kosmos.Fixture {
+ FromGoneTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
index a646bc6fec44..c9c17d98fce1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.content.applicationContext
import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
var Kosmos.keyguardSurfaceBehindInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@ var Kosmos.keyguardSurfaceBehindInteractor by
inWindowLauncherUnlockAnimationInteractor
},
swipeToDismissInteractor = swipeToDismissInteractor,
+ notificationLaunchInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index e4d115e16b6a..0c38fd9c37a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -30,5 +30,6 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
fromPrimaryBouncerTransitionInteractor =
Lazy { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index 0207280dd30c..d84988da7cc3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
val Kosmos.windowManagerLockscreenVisibilityInteractor by
Kosmos.Fixture {
@@ -26,5 +27,6 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by
surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d376f126e2c9..24bb9c5008bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -38,6 +38,9 @@ val Kosmos.keyguardRootViewModel by Fixture {
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
new file mode 100644
index 000000000000..5dc03338f19b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.shadeBackActionInteractor by
+ Kosmos.Fixture {
+ ShadeBackActionInteractorImpl(
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
new file mode 100644
index 000000000000..5638cfc69287
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationLaunchAnimationRepository by
+ Kosmos.Fixture { NotificationLaunchAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
new file mode 100644
index 000000000000..0d84bab8941c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository
+
+val Kosmos.notificationLaunchAnimationInteractor by
+ Kosmos.Fixture {
+ NotificationLaunchAnimationInteractor(
+ repository = notificationLaunchAnimationRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 549a77513e9c..30d4105e8ca1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,13 +19,16 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +44,9 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
shadeInteractor = shadeInteractor,
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
new file mode 100644
index 000000000000..5054e29534e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
+
+ private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
+ return settings.map { it.getOrDefault(name, defaultValue) }
+ }
+
+ fun setBoolSettingForActiveUser(name: String, value: Boolean) {
+ settings.value = settings.value.toMutableMap().apply { this[name] = value }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
new file mode 100644
index 000000000000..94b2bdf63608
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userAwareSecureSettingsRepository by
+ Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f68baf5546d7..592c6f4cf2e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -30,11 +30,11 @@ public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener
private final List<FlashlightListener> callbacks = new ArrayList<>();
@VisibleForTesting
- public boolean isAvailable;
+ public boolean isAvailable = true;
@VisibleForTesting
- public boolean isEnabled;
+ public boolean isEnabled = false;
@VisibleForTesting
- public boolean hasFlashlight;
+ public boolean hasFlashlight = true;
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
@@ -52,16 +52,26 @@ public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener
callbacks.forEach(FlashlightListener::onFlashlightError);
}
+ /**
+ * Used to decide if tile should be shown or gone
+ * @return available/unavailable
+ */
@Override
public boolean hasFlashlight() {
return hasFlashlight;
}
+ /**
+ * @param newState active/inactive
+ */
@Override
public void setFlashlight(boolean newState) {
callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
+ /**
+ * @return temporary availability
+ */
@Override
public boolean isAvailable() {
return isAvailable;
@@ -76,6 +86,9 @@ public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener
public void addCallback(FlashlightListener listener) {
super.addCallback(listener);
callbacks.add(listener);
+
+ listener.onFlashlightAvailabilityChanged(isAvailable());
+ listener.onFlashlightChanged(isEnabled());
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index cf414d145db1..3645c40aeda2 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -254,6 +254,14 @@ public final class PresentationStatsEventLogger {
mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
}
+ /**
+ * Set webview_requested_credential
+ */
+ public void maybeSetWebviewRequestedCredential(boolean webviewRequestedCredential) {
+ mEventInternal.ifPresent(event ->
+ event.mWebviewRequestedCredential = webviewRequestedCredential);
+ }
+
public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
mEventInternal.ifPresent(event -> {
if (event.mCountShown == 0) {
@@ -578,7 +586,8 @@ public final class PresentationStatsEventLogger {
+ " mDetectionPreference=" + event.mDetectionPreference
+ " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
+ " mAppPackageUid=" + mCallingAppUid
- + " mIsCredentialRequest=" + event.mIsCredentialRequest);
+ + " mIsCredentialRequest=" + event.mIsCredentialRequest
+ + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -618,7 +627,8 @@ public final class PresentationStatsEventLogger {
event.mDetectionPreference,
event.mFieldClassificationRequestId,
mCallingAppUid,
- event.mIsCredentialRequest);
+ event.mIsCredentialRequest,
+ event.mWebviewRequestedCredential);
mEventInternal = Optional.empty();
}
@@ -653,6 +663,7 @@ public final class PresentationStatsEventLogger {
@DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
int mFieldClassificationRequestId = -1;
boolean mIsCredentialRequest = false;
+ boolean mWebviewRequestedCredential = false;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 049feeed2fa1..83d9cdbd2843 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -18,6 +18,7 @@ package com.android.server.autofill;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.AutofillService.WEBVIEW_REQUESTED_CREDENTIAL_KEY;
import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
@@ -5516,8 +5517,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mResponses.put(requestId, newResponse);
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
+ boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean(
+ WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
List<Dataset> datasetList = newResponse.getDatasets();
+ mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(sIdCounterForPcc.get());
mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index b2716ecc0cfc..d580f3a7d7d8 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,6 +15,7 @@
*/
package com.android.server.autofill.ui;
+import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static com.android.server.autofill.Helper.paramsToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -31,6 +32,7 @@ import android.graphics.drawable.Drawable;
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetFieldFilter;
import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
import android.text.TextUtils;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
@@ -79,6 +81,7 @@ final class FillUi {
com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill;
+ private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
private static final TypedValue sTempTypedValue = new TypedValue();
@@ -211,7 +214,11 @@ final class FillUi {
if (sVerbose) {
Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
}
- } else {
+ } else if (Flags.autofillCredmanIntegration() && (
+ (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+ mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
+ }
+ else {
mVisibleDatasetsMaxCount = mContext.getResources()
.getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 6a63b3a9db24..71f2b9e8e10b 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -32,4 +32,13 @@ flag {
description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
bug: "320633449"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_increase_datatypes_for_agent_logging"
+ namespace: "onboarding"
+ description: "Increase the number of a supported datatypes that an agent can define for its "
+ "logger."
+ bug: "296844513"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ea1b0f5f66f7..e7fae2483d16 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -48,6 +48,7 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -3597,6 +3598,13 @@ class StorageManagerService extends IStorageManager.Stub
return mInternalStorageSize;
}
+ @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Override
+ public int getInternalStorageRemainingLifetime() throws RemoteException {
+ super.getInternalStorageRemainingLifetime_enforcePermission();
+ return mVold.getStorageRemainingLifetime();
+ }
+
/**
* Enforces that the caller is the {@link ExternalStorageService}
*
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 2aed8476d031..0f75ad481662 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -31,7 +31,7 @@ option java_package com.android.server.am
30017 am_low_memory (Num Processes|1|1)
# Kill a process to reclaim memory.
-30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2)
# Discard an undelivered serialized broadcast (timeout/ANR/crash)
30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java
index 1a692df94dd0..ac96bdc1a27d 100644
--- a/services/core/java/com/android/server/am/PhantomProcessRecord.java
+++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java
@@ -105,6 +105,11 @@ public final class PhantomProcessRecord {
}
}
+ public long getRss(int pid) {
+ long[] rss = Process.getRss(pid);
+ return (rss != null && rss.length > 0) ? rss[0] : 0;
+ }
+
@GuardedBy("mLock")
void killLocked(String reason, boolean noisy) {
if (!mKilled) {
@@ -115,7 +120,7 @@ public final class PhantomProcessRecord {
}
if (mPid > 0) {
EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
- mPid, mProcessName, mAdj, reason);
+ mPid, mProcessName, mAdj, reason, getRss(mPid));
if (!Process.supportsPidFd()) {
onProcDied(false);
} else {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cd2e441cf351..3adea7a929cd 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4986,19 +4986,7 @@ public final class ProcessList {
}
void dispatchProcessStarted(ProcessRecord app, int pid) {
- int i = mProcessObservers.beginBroadcast();
- while (i > 0) {
- i--;
- final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
- if (observer != null) {
- try {
- observer.onProcessStarted(pid, app.uid, app.info.uid,
- app.info.packageName, app.processName);
- } catch (RemoteException e) {
- }
- }
- }
- mProcessObservers.finishBroadcast();
+ // TODO(b/323959187) Add the implementation.
}
void dispatchProcessDied(int pid, int uid) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index de6f034b62ee..d23d9fb16d6c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1217,6 +1217,11 @@ class ProcessRecord implements WindowProcessListener {
}
}
+ public long getRss(int pid) {
+ long[] rss = Process.getRss(pid);
+ return (rss != null && rss.length > 0) ? rss[0] : 0;
+ }
+
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
@@ -1260,7 +1265,7 @@ class ProcessRecord implements WindowProcessListener {
if (mPid > 0) {
mService.mProcessList.noteAppKill(this, reasonCode, subReason, description);
EventLog.writeEvent(EventLogTags.AM_KILL,
- userId, mPid, processName, mState.getSetAdj(), reason);
+ userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid));
Process.killProcessQuiet(mPid);
killProcessGroupIfNecessaryLocked(asyncKPG);
} else {
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
new file mode 100644
index 000000000000..a923daaa5a51
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * This class provides the handler to process biometric operations.
+ */
+public class BiometricHandlerProvider {
+ private static final BiometricHandlerProvider sBiometricHandlerProvider =
+ new BiometricHandlerProvider();
+
+ private final Handler mBiometricsCallbackHandler;
+ private final Handler mFingerprintHandler;
+ private final Handler mFaceHandler;
+
+ /**
+ * @return an instance of {@link BiometricHandlerProvider} which contains the three
+ * threads needed for running biometric operations
+ */
+ public static BiometricHandlerProvider getInstance() {
+ return sBiometricHandlerProvider;
+ }
+
+ private BiometricHandlerProvider() {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
+ mFingerprintHandler = getNewHandler("FingerprintHandler");
+ mFaceHandler = getNewHandler("FaceHandler");
+ }
+
+ /**
+ * @return the handler to process all biometric callback operations
+ */
+ public synchronized Handler getBiometricCallbackHandler() {
+ return mBiometricsCallbackHandler;
+ }
+
+ /**
+ * @return the handler to process all face related biometric operations
+ */
+ public synchronized Handler getFaceHandler() {
+ return mFaceHandler;
+ }
+
+ /**
+ * @return the handler to process all fingerprint related biometric operations
+ */
+ public synchronized Handler getFingerprintHandler() {
+ return mFingerprintHandler;
+ }
+
+ private Handler getNewHandler(String tag) {
+ if (Flags.deHidl()) {
+ HandlerThread handlerThread = new HandlerThread(tag);
+ handlerThread.start();
+ return new Handler(handlerThread.getLooper());
+ }
+ return new Handler(Looper.getMainLooper());
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 91a68ea67b3b..fc948da260e6 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,7 +62,6 @@ import android.os.Build;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -140,7 +139,7 @@ public class BiometricService extends SystemService {
// The current authentication session, null if idle/done.
@VisibleForTesting
AuthSession mAuthSession;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final BiometricCameraManager mBiometricCameraManager;
@@ -1113,14 +1112,16 @@ public class BiometricService extends SystemService {
* @param context The system server context.
*/
public BiometricService(Context context) {
- this(context, new Injector());
+ this(context, new Injector(), BiometricHandlerProvider.getInstance());
}
@VisibleForTesting
- BiometricService(Context context, Injector injector) {
+ BiometricService(Context context, Injector injector,
+ BiometricHandlerProvider biometricHandlerProvider) {
super(context);
mInjector = injector;
+ mHandler = biometricHandlerProvider.getBiometricCallbackHandler();
mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
mImpl = new BiometricServiceWrapper();
mEnabledOnKeyguardCallbacks = new ArrayList<>();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index d01c2687b1ff..f469f6224e4c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -39,7 +39,6 @@ import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -53,6 +52,7 @@ import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -124,6 +124,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
private final BiometricContext mBiometricContext;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final BiometricHandlerProvider mBiometricHandlerProvider;
@Nullable
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
@@ -166,8 +168,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull BiometricContext biometricContext,
boolean resetLockoutRequiresChallenge) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
- lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
- resetLockoutRequiresChallenge, false /* testHalEnabled */);
+ lockoutResetDispatcher, biometricContext, null /* daemon */,
+ BiometricHandlerProvider.getInstance(), resetLockoutRequiresChallenge,
+ false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@@ -178,7 +181,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
@Nullable IFace daemon,
- @NonNull Handler handler,
+ @NonNull BiometricHandlerProvider biometricHandlerProvider,
boolean resetLockoutRequiresChallenge,
boolean testHalEnabled) {
mContext = context;
@@ -187,7 +190,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
- mHandler = handler;
+ mHandler = biometricHandlerProvider.getFaceHandler();
} else {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -199,18 +202,12 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
mTestHalEnabled = testHalEnabled;
+ mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initSensors(resetLockoutRequiresChallenge, props);
}
- @NonNull
- private static Handler getHandler() {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
-
private void initAuthenticationBroadcastReceiver() {
new AuthenticationStatsBroadcastReceiver(
mContext,
@@ -622,15 +619,29 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@Override
public void onClientStarted(
BaseClientMonitor clientMonitor) {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+ requestId));
+ } else {
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ }
}
@Override
public void onClientFinished(
BaseClientMonitor clientMonitor,
boolean success) {
- mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, client.wasAuthSuccessful());
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ client.wasAuthSuccessful()));
+ } else {
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId),
+ sensorId, requestId, client.wasAuthSuccessful());
+ }
}
});
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0388d1c4f21..fd938ed9c389 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,7 +46,6 @@ import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -60,6 +59,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -129,11 +129,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final BiometricHandlerProvider mBiometricHandlerProvider;
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
// TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@Nullable private ISidefpsController mSidefpsController;
- private AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
@Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -175,8 +176,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
- false /* testHalEnabled */);
+ null /* daemon */, BiometricHandlerProvider.getInstance(),
+ resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */);
}
@VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -187,7 +188,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
@Nullable IFingerprint daemon,
- @NonNull Handler handler,
+ @NonNull BiometricHandlerProvider biometricHandlerProvider,
boolean resetLockoutRequiresHardwareAuthToken,
boolean testHalEnabled) {
mContext = context;
@@ -196,7 +197,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mHalInstanceName = halInstanceName;
mFingerprintSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
- mHandler = handler;
+ mHandler = biometricHandlerProvider.getFingerprintHandler();
} else {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -207,18 +208,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
mTestHalEnabled = testHalEnabled;
+ mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
}
- @NonNull
- private static Handler getHandler() {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
-
private void initAuthenticationBroadcastReceiver() {
new AuthenticationStatsBroadcastReceiver(
mContext,
@@ -620,7 +615,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
mBiometricStateCallback.onClientStarted(clientMonitor);
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+ requestId));
+ } else {
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ }
}
@Override
@@ -632,8 +633,15 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
- mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, success);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ success));
+ } else {
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
+ }
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8aa038ff0ca4..3bd1e1a43629 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2515,6 +2515,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
}
+ mSettings.putSelectedDefaultDeviceInputMethod(null);
return defaultDeviceMethodId;
}
@@ -3179,6 +3180,26 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
+
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ String ime = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ String defaultDeviceIme = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+ + " device input method for user " + mSettings.getUserId()
+ + " - restoring " + defaultDeviceIme);
+ }
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+ mSettings.getUserId());
+ SecureSettingsWrapper.putString(
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ }
+ }
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -5339,7 +5360,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
InputMethodInfoUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodList());
mSettings.putSelectedDefaultDeviceInputMethod(
- newDefaultIme == null ? "" : newDefaultIme.getId());
+ newDefaultIme == null ? null : newDefaultIme.getId());
}
// Previous state was enabled.
return true;
@@ -5382,6 +5403,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+ mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+ mDisplayIdToShowIme = INVALID_DISPLAY;
+ mSettings.putSelectedDefaultDeviceInputMethod(null);
+
InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
@@ -6565,6 +6590,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Reset selected IME.
settings.putSelectedInputMethod(nextIme);
+ settings.putSelectedDefaultDeviceInputMethod(null);
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
}
out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002be344f..e444db1b79e8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -88,12 +87,6 @@ final class InputMethodSettings {
mMethodMap = methodMap;
mMethodList = methodMap.values();
mUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
}
@AnyThread
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index dff02bf711cd..e349fa36368d 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_GROUP_SUMMARY;
@@ -23,15 +24,24 @@ import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import android.annotation.NonNull;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* NotificationManagerService helper for auto-grouping notifications.
@@ -41,6 +51,8 @@ public class GroupHelper {
protected static final String AUTOGROUP_KEY = "ranker_group";
+ protected static final int FLAG_INVALID = -1;
+
// Flags that all autogroup summaries have
protected static final int BASE_FLAGS =
FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
@@ -51,17 +63,22 @@ public class GroupHelper {
private final Callback mCallback;
private final int mAutoGroupAtCount;
+ private final Context mContext;
+ private final PackageManager mPackageManager;
// Only contains notifications that are not explicitly grouped by the app (aka no group or
// sort key).
// userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
@GuardedBy("mUngroupedNotifications")
- private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+ private final ArrayMap<String, ArrayMap<String, NotificationAttributes>> mUngroupedNotifications
= new ArrayMap<>();
- public GroupHelper(int autoGroupAtCount, Callback callback) {
+ public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
+ Callback callback) {
mAutoGroupAtCount = autoGroupAtCount;
mCallback = callback;
+ mContext = context;
+ mPackageManager = packageManager;
}
private String generatePackageKey(int userId, String pkg) {
@@ -70,15 +87,16 @@ public class GroupHelper {
@VisibleForTesting
@GuardedBy("mUngroupedNotifications")
- protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+ protected int getAutogroupSummaryFlags(
+ @NonNull final ArrayMap<String, NotificationAttributes> children) {
boolean allChildrenHasFlag = children.size() > 0;
int anyChildFlagSet = 0;
for (int i = 0; i < children.size(); i++) {
- if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+ if (!hasAnyFlag(children.valueAt(i).flags, ALL_CHILDREN_FLAG)) {
allChildrenHasFlag = false;
}
- if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
- anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+ if (hasAnyFlag(children.valueAt(i).flags, ANY_CHILDREN_FLAGS)) {
+ anyChildFlagSet |= (children.valueAt(i).flags & ANY_CHILDREN_FLAGS);
}
}
return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
@@ -95,7 +113,6 @@ public class GroupHelper {
} else {
maybeUngroup(sbn, false, sbn.getUserId());
}
-
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
}
@@ -121,25 +138,47 @@ public class GroupHelper {
private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
int flags = 0;
List<String> notificationsToGroup = new ArrayList<>();
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
- children.put(sbn.getKey(), sbn.getNotification().flags);
+ NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ children.put(sbn.getKey(), attr);
mUngroupedNotifications.put(key, children);
if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
flags = getAutogroupSummaryFlags(children);
notificationsToGroup.addAll(children.keySet());
+ childrenAttr.addAll(children.values());
}
}
if (notificationsToGroup.size() > 0) {
if (autogroupSummaryExists) {
- mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+ NotificationAttributes attr = new NotificationAttributes(flags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ if (Flags.autogroupSummaryIconUpdate()) {
+ attr = updateAutobundledSummaryIcon(sbn.getPackageName(), childrenAttr, attr);
+ }
+
+ mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr);
} else {
- mCallback.addAutoGroupSummary(
- sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+ Icon summaryIcon = sbn.getNotification().getSmallIcon();
+ int summaryIconColor = sbn.getNotification().color;
+ if (Flags.autogroupSummaryIconUpdate()) {
+ // Calculate the initial summary icon and icon color
+ NotificationAttributes iconAttr = getAutobundledSummaryIconAndColor(
+ sbn.getPackageName(), childrenAttr);
+ summaryIcon = iconAttr.icon;
+ summaryIconColor = iconAttr.iconColor;
+ }
+
+ NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon,
+ summaryIconColor);
+ mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
+ attr);
}
for (String key : notificationsToGroup) {
mCallback.addAutoGroup(key);
@@ -154,16 +193,17 @@ public class GroupHelper {
* (b) if we need to remove our autogroup overlay for this notification
* (c) we need to remove the autogroup summary
*
- * And updates the internal state of un-app-grouped notifications and their flags
+ * And updates the internal state of un-app-grouped notifications and their flags.
*/
private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
boolean removeSummary = false;
- int summaryFlags = 0;
+ int summaryFlags = FLAG_INVALID;
boolean updateSummaryFlags = false;
boolean removeAutogroupOverlay = false;
+ List<NotificationAttributes> childrenAttrs = new ArrayList<>();
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
if (children.size() == 0) {
return;
@@ -173,7 +213,7 @@ public class GroupHelper {
if (children.containsKey(sbn.getKey())) {
// if this notification was contributing flags that aren't covered by other
// children to the summary, reevaluate flags for the summary
- int flags = children.remove(sbn.getKey());
+ int flags = children.remove(sbn.getKey()).flags;
// this
if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
updateSummaryFlags = true;
@@ -188,14 +228,29 @@ public class GroupHelper {
// If there are no more children left to autogroup, remove the summary
if (children.size() == 0) {
removeSummary = true;
+ } else {
+ childrenAttrs.addAll(children.values());
}
}
}
+
if (removeSummary) {
mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
} else {
- if (updateSummaryFlags) {
- mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+ NotificationAttributes attr = new NotificationAttributes(summaryFlags,
+ sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+ boolean iconUpdated = false;
+ if (Flags.autogroupSummaryIconUpdate()) {
+ NotificationAttributes newAttr = updateAutobundledSummaryIcon(sbn.getPackageName(),
+ childrenAttrs, attr);
+ if (!newAttr.equals(attr)) {
+ iconUpdated = true;
+ attr = newAttr;
+ }
+ }
+
+ if (updateSummaryFlags || iconUpdated) {
+ mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr);
}
}
if (removeAutogroupOverlay) {
@@ -207,17 +262,139 @@ public class GroupHelper {
int getNotGroupedByAppCount(int userId, String pkg) {
synchronized (mUngroupedNotifications) {
String key = generatePackageKey(userId, pkg);
- final ArrayMap<String, Integer> children =
+ final ArrayMap<String, NotificationAttributes> children =
mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
return children.size();
}
}
+ NotificationAttributes getAutobundledSummaryIconAndColor(@NonNull String packageName,
+ @NonNull List<NotificationAttributes> childrenAttr) {
+ Icon newIcon = null;
+ boolean childrenHaveSameIcon = true;
+ int newColor = Notification.COLOR_INVALID;
+ boolean childrenHaveSameColor = true;
+
+ // Both the icon drawable and the icon background color are updated according to this rule:
+ // - if all child icons are identical => use the common icon
+ // - if child icons are different: use the monochromatic app icon, if exists.
+ // Otherwise fall back to a generic icon representing a stack.
+ for (NotificationAttributes state: childrenAttr) {
+ // Check for icon
+ if (newIcon == null) {
+ newIcon = state.icon;
+ } else {
+ if (!newIcon.sameAs(state.icon)) {
+ childrenHaveSameIcon = false;
+ }
+ }
+ // Check for color
+ if (newColor == Notification.COLOR_INVALID) {
+ newColor = state.iconColor;
+ } else {
+ if (newColor != state.iconColor) {
+ childrenHaveSameColor = false;
+ }
+ }
+ }
+ if (!childrenHaveSameIcon) {
+ newIcon = getMonochromeAppIcon(packageName);
+ }
+ if (!childrenHaveSameColor) {
+ newColor = COLOR_DEFAULT;
+ }
+
+ return new NotificationAttributes(0, newIcon, newColor);
+ }
+
+ NotificationAttributes updateAutobundledSummaryIcon(@NonNull String packageName,
+ @NonNull List<NotificationAttributes> childrenAttr,
+ @NonNull NotificationAttributes oldAttr) {
+ NotificationAttributes newAttr = getAutobundledSummaryIconAndColor(packageName,
+ childrenAttr);
+ Icon newIcon = newAttr.icon;
+ int newColor = newAttr.iconColor;
+ if (newAttr.icon == null) {
+ newIcon = oldAttr.icon;
+ }
+ if (newAttr.iconColor == Notification.COLOR_INVALID) {
+ newColor = oldAttr.iconColor;
+ }
+
+ return new NotificationAttributes(oldAttr.flags, newIcon, newColor);
+ }
+
+ /**
+ * Get the monochrome app icon for an app from the adaptive launcher icon
+ * or a fallback generic icon for autogroup summaries.
+ *
+ * @param pkg packageName of the app
+ * @return a monochrome app icon or a fallback generic icon
+ */
+ @NonNull
+ Icon getMonochromeAppIcon(@NonNull final String pkg) {
+ Icon monochromeIcon = null;
+ final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+ try {
+ final Drawable appIcon = mPackageManager.getApplicationIcon(pkg);
+ if (appIcon instanceof AdaptiveIconDrawable) {
+ if (((AdaptiveIconDrawable) appIcon).getMonochrome() != null) {
+ monochromeIcon = Icon.createWithResourceAdaptiveDrawable(pkg,
+ ((AdaptiveIconDrawable) appIcon).getSourceDrawableResId(), true,
+ -2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Failed to getApplicationIcon() in getMonochromeAppIcon()", e);
+ }
+ if (monochromeIcon != null) {
+ return monochromeIcon;
+ } else {
+ return Icon.createWithResource(mContext, fallbackIconResId);
+ }
+ }
+
+ protected static class NotificationAttributes {
+ public final int flags;
+ public final int iconColor;
+ public final Icon icon;
+
+ public NotificationAttributes(int flags, Icon icon, int iconColor) {
+ this.flags = flags;
+ this.icon = icon;
+ this.iconColor = iconColor;
+ }
+
+ public NotificationAttributes(@NonNull NotificationAttributes attr) {
+ this.flags = attr.flags;
+ this.icon = attr.icon;
+ this.iconColor = attr.iconColor;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof NotificationAttributes that)) {
+ return false;
+ }
+ return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(flags, iconColor, icon);
+ }
+ }
+
protected interface Callback {
void addAutoGroup(String key);
void removeAutoGroup(String key);
- void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
+
+ void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+ NotificationAttributes summaryAttr);
void removeAutoGroupSummary(int user, String pkg);
- void updateAutogroupSummary(int userId, String pkg, int flags);
+ void updateAutogroupSummary(int userId, String pkg, NotificationAttributes summaryAttr);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 638382e1f5ae..91706cf46fc9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -227,6 +227,7 @@ import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
@@ -341,6 +342,7 @@ import com.android.server.SystemService;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.notification.toast.CustomToastRecord;
@@ -996,17 +998,19 @@ public class NotificationManagerService extends SystemService {
}
/**
- * This method will update the flags of the summary.
+ * This method will update the flags and/or the icon of the summary.
* It will set it to FLAG_ONGOING_EVENT if any of its group members
- * has the same flag. It will delete the flag otherwise
+ * has the same flag. It will delete the flag otherwise.
+ * It will update the summary notification icon if the group children's
+ * icons are different.
* @param userId user id of the autogroup summary
* @param pkg package of the autogroup summary
- * @param flags the new flags for this summary
+ * @param summaryAttr the new flags and/or icon & color for this summary
* @param isAppForeground true if the app is currently in the foreground.
*/
@GuardedBy("mNotificationLock")
- protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
- boolean isAppForeground) {
+ protected void updateAutobundledSummaryLocked(int userId, String pkg,
+ NotificationAttributes summaryAttr, boolean isAppForeground) {
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries == null) {
return;
@@ -1020,8 +1024,16 @@ public class NotificationManagerService extends SystemService {
return;
}
int oldFlags = summary.getSbn().getNotification().flags;
- if (oldFlags != flags) {
- summary.getSbn().getNotification().flags = flags;
+
+ boolean iconUpdated =
+ !summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon())
+ || summaryAttr.iconColor != summary.getSbn().getNotification().color;
+
+ if (oldFlags != summaryAttr.flags || iconUpdated) {
+ summary.getSbn().getNotification().flags =
+ summaryAttr.flags != GroupHelper.FLAG_INVALID ? summaryAttr.flags : oldFlags;
+ summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon);
+ summary.getSbn().getNotification().color = summaryAttr.iconColor;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -2873,7 +2885,8 @@ public class NotificationManagerService extends SystemService {
private GroupHelper getGroupHelper() {
mAutoGroupAtCount =
getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
- return new GroupHelper(mAutoGroupAtCount, new GroupHelper.Callback() {
+ return new GroupHelper(getContext(), getContext().getPackageManager(),
+ mAutoGroupAtCount, new GroupHelper.Callback() {
@Override
public void addAutoGroup(String key) {
synchronized (mNotificationLock) {
@@ -2890,8 +2903,9 @@ public class NotificationManagerService extends SystemService {
@Override
public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- int flags) {
- NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+ NotificationAttributes summaryAttr) {
+ NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey,
+ summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor);
if (r != null) {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2908,11 +2922,12 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void updateAutogroupSummary(int userId, String pkg, int flags) {
+ public void updateAutogroupSummary(int userId, String pkg,
+ NotificationAttributes summaryAttr) {
boolean isAppForeground = pkg != null
&& mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
- updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
+ updateAutobundledSummaryLocked(userId, pkg, summaryAttr, isAppForeground);
}
}
});
@@ -4880,14 +4895,14 @@ public class NotificationManagerService extends SystemService {
continue;
}
notificationsRapidlyCleared = notificationsRapidlyCleared
- || isNotificationRecent(r);
+ || isNotificationRecent(r.getUpdateTimeMs());
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
r.getSbn().getId(), userId, reason);
}
} else {
for (NotificationRecord notificationRecord : mNotificationList) {
- if (isNotificationRecent(notificationRecord)) {
+ if (isNotificationRecent(notificationRecord.getUpdateTimeMs())) {
notificationsRapidlyCleared = true;
break;
}
@@ -4913,14 +4928,6 @@ public class NotificationManagerService extends SystemService {
}
}
- private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) {
- if (!rapidClearNotificationsByListenerAppOpEnabled()) {
- return false;
- }
- return notificationRecord.getFreshnessMs(System.currentTimeMillis())
- < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
- }
-
/**
* Handle request from an approved listener to re-enable itself.
*
@@ -5044,12 +5051,11 @@ public class NotificationManagerService extends SystemService {
@Override
public void snoozeNotificationUntilContextFromListener(INotificationListener token,
String key, String snoozeCriterionId) {
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
- }
+ snoozeNotificationInt(callingUid, token, key, SNOOZE_UNTIL_UNSPECIFIED,
+ snoozeCriterionId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5063,12 +5069,10 @@ public class NotificationManagerService extends SystemService {
@Override
public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
long duration) {
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, duration, null, info);
- }
+ snoozeNotificationInt(callingUid, token, key, duration, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6529,7 +6533,7 @@ public class NotificationManagerService extends SystemService {
// Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
- int flagsToSet) {
+ int flagsToSet, Icon summaryIcon, int summaryIconColor) {
NotificationRecord summaryRecord = null;
boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
synchronized (mNotificationLock) {
@@ -6555,14 +6559,15 @@ public class NotificationManagerService extends SystemService {
final Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
+
final Notification summaryNotification =
- new Notification.Builder(getContext(), channelId)
- .setSmallIcon(adjustedSbn.getNotification().getSmallIcon())
+ new Notification.Builder(getContext(), channelId)
+ .setSmallIcon(summaryIcon)
.setGroupSummary(true)
.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
.setGroup(GroupHelper.AUTOGROUP_KEY)
.setFlag(flagsToSet, true)
- .setColor(adjustedSbn.getNotification().color)
+ .setColor(summaryIconColor)
.build();
summaryNotification.extras.putAll(extras);
Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -10326,16 +10331,22 @@ public class NotificationManagerService extends SystemService {
}
}
- void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
- ManagedServiceInfo listener) {
- if (listener == null) {
- return;
- }
- String listenerName = listener.component.toShortString();
- if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
- return;
- }
+ void snoozeNotificationInt(int callingUid, INotificationListener token, String key,
+ long duration, String snoozeCriterionId) {
+ final String packageName;
+ final long notificationUpdateTimeMs;
+
synchronized (mNotificationLock) {
+ final ManagedServiceInfo listener = mListeners.checkServiceTokenLocked(token);
+ if (listener == null) {
+ return;
+ }
+ packageName = listener.component.getPackageName();
+ String listenerName = listener.component.toShortString();
+ if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
+ return;
+ }
+
final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
if (r == null) {
return;
@@ -10343,14 +10354,20 @@ public class NotificationManagerService extends SystemService {
if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
return;
}
+ notificationUpdateTimeMs = r.getUpdateTimeMs();
+
+ if (DBG) {
+ Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
+ snoozeCriterionId, listenerName));
+ }
+ // Needs to post so that it can cancel notifications not yet enqueued.
+ mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
}
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
- snoozeCriterionId, listenerName));
+ if (isNotificationRecent(notificationUpdateTimeMs)) {
+ mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ callingUid, packageName, /* attributionTag= */ null, /* message= */ null);
}
- // Needs to post so that it can cancel notifications not yet enqueued.
- mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
}
void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) {
@@ -10362,6 +10379,14 @@ public class NotificationManagerService extends SystemService {
handleSavePolicyFile();
}
+ private boolean isNotificationRecent(long notificationUpdateTimeMs) {
+ if (!rapidClearNotificationsByListenerAppOpEnabled()) {
+ return false;
+ }
+ return System.currentTimeMillis() - notificationUpdateTimeMs
+ < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
+ }
+
@GuardedBy("mNotificationLock")
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles, int mustNotHaveFlags) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ebaf516f4066..4fb0c220708f 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -42,6 +42,7 @@ import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
@@ -155,6 +156,9 @@ final class InstallRequest {
@NonNull
private final ArrayList<String> mWarnings = new ArrayList<>();
+ @Nullable
+ private DomainSet mPreVerifiedDomains;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -172,6 +176,7 @@ final class InstallRequest {
mIsInstallInherit = params.mIsInherit;
mSessionId = params.mSessionId;
mRequireUserAction = params.mRequireUserAction;
+ mPreVerifiedDomains = params.mPreVerifiedDomains;
}
// Install existing package as user
@@ -875,6 +880,11 @@ final class InstallRequest {
}
}
+ @Nullable
+ public DomainSet getPreVerifiedDomains() {
+ return mPreVerifiedDomains;
+ }
+
public void addWarning(@NonNull String warning) {
mWarnings.add(warning);
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e970d2c79b05..4cbd3ad45dc7 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -40,6 +40,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
import android.os.Environment;
import android.os.Trace;
import android.os.UserHandle;
@@ -98,6 +99,8 @@ class InstallingSession {
final int mSessionId;
final int mRequireUserAction;
final boolean mApplicationEnabledSettingPersistent;
+ @Nullable
+ final DomainSet mPreVerifiedDomains;
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,12 +133,13 @@ class InstallingSession {
mSessionId = -1;
mRequireUserAction = USER_ACTION_UNSPECIFIED;
mApplicationEnabledSettingPersistent = false;
+ mPreVerifiedDomains = null;
}
InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
- PackageLite packageLite, PackageManagerService pm) {
+ PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
mPm = pm;
mUser = user;
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -163,6 +167,7 @@ class InstallingSession {
mSessionId = sessionId;
mRequireUserAction = sessionParams.requireUserAction;
mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
+ mPreVerifiedDomains = preVerifiedDomains;
}
@Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43328fcfb9a0..984a6299cc81 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1676,6 +1676,8 @@ public class LauncherAppsService extends SystemService {
private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+ appMarketIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return buildIntentSenderForUser(appMarketIntent, user);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
index d40a7157253b..4b98e3408cd3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.verify.domain.DomainSet;
import com.android.internal.util.IndentingPrintWriter;
@@ -76,6 +77,7 @@ public final class PackageInstallerHistoricalSession {
private final boolean mSessionFailed;
private final int mSessionErrorCode;
private final String mSessionErrorMessage;
+ private final String mPreVerifiedDomains;
PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
String originalInstallerPackageName, InstallSource installSource, int installerUid,
@@ -86,7 +88,7 @@ public final class PackageInstallerHistoricalSession {
String finalMessage, SessionParams params, int parentSessionId,
int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
- PreapprovalDetails preapprovalDetails) {
+ PreapprovalDetails preapprovalDetails, DomainSet preVerifiedDomains) {
this.sessionId = sessionId;
this.userId = userId;
this.mOriginalInstallerUid = originalInstallerUid;
@@ -128,6 +130,11 @@ public final class PackageInstallerHistoricalSession {
} else {
this.mPreapprovalDetails = null;
}
+ if (preVerifiedDomains != null) {
+ this.mPreVerifiedDomains = String.join(",", preVerifiedDomains.getDomains());
+ } else {
+ this.mPreVerifiedDomains = null;
+ }
}
void dump(IndentingPrintWriter pw) {
@@ -170,6 +177,7 @@ public final class PackageInstallerHistoricalSession {
pw.printPair("mSessionErrorCode", mSessionErrorCode);
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+ pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
pw.println();
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index abea56bfa433..6e4f19925b59 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1026,7 +1026,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
- false, false, false, PackageManager.INSTALL_UNKNOWN, "");
+ false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 27c3dad23450..c860b5ae79f6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -113,6 +113,7 @@ import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
@@ -241,6 +242,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"whitelisted-restricted-permission";
private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE =
"auto-revoke-permissions-mode";
+
+ static final String TAG_PRE_VERIFIED_DOMAINS = "preVerifiedDomains";
private static final String ATTR_SESSION_ID = "sessionId";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
@@ -298,6 +301,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
"applicationEnabledSettingPersistent";
+ private static final String ATTR_DOMAIN = "domain";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -365,6 +369,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
private static final long THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT = 240618202L;
+ /**
+ * Configurable maximum number of pre-verified domains allowed to be added to the session.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
+ "pre_verified_domains_count_limit";
+ /**
+ * Configurable maximum string length of each pre-verified domain.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
+ "pre_verified_domain_length_limit";
+ /** Default max number of pre-verified domains */
+ private static final long DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT = 1000;
+ /** Default max string length of each pre-verified domain */
+ private static final long DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT = 256;
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -506,6 +529,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private int mUserActionRequirement;
+ @GuardedBy("mLock")
+ private DomainSet mPreVerifiedDomains;
+
static class FileEntry {
private final int mIndex;
private final InstallationFile mFile;
@@ -936,6 +962,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
getInstallSource().mInstallerPackageName, mInstallerUid);
}
+ private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
+ final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+ return false;
+ }
+ String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
+ if (emergencyInstaller == null || !ArrayUtils.contains(
+ snapshot.getPackagesForUid(mInstallerUid),
+ emergencyInstaller)) {
+ return false;
+ }
+ return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
+ mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ }
+
private static final int USER_ACTION_NOT_NEEDED = 0;
private static final int USER_ACTION_REQUIRED = 1;
private static final int USER_ACTION_PENDING_APK_PARSING = 2;
@@ -1020,6 +1061,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
getInstallerPackageName());
final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
+ final boolean isEmergencyInstall =
+ isEmergencyInstallerEnabled(packageName, snapshot);
final boolean isPermissionGranted = isInstallPermissionGranted
|| (isUpdatePermissionGranted && isUpdate)
|| (isSelfUpdatePermissionGranted && isSelfUpdate)
@@ -1036,7 +1079,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
- || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
+ || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
if (noUserActionNecessary) {
return userActionNotTypicallyNeededResponse;
@@ -1089,7 +1132,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
boolean prepared, boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int sessionErrorCode,
- String sessionErrorMessage) {
+ String sessionErrorMessage, DomainSet preVerifiedDomains) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1151,6 +1194,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mSessionErrorMessage =
sessionErrorMessage != null ? sessionErrorMessage : "";
mStagedSession = params.isStaged ? new StagedSession() : null;
+ mPreVerifiedDomains = preVerifiedDomains;
if (isDataLoaderInstallation()) {
if (isApexSession()) {
@@ -1198,7 +1242,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
- mSessionErrorMessage, mPreapprovalDetails);
+ mSessionErrorMessage, mPreapprovalDetails, mPreVerifiedDomains);
}
}
@@ -3135,7 +3179,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) {
return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
- user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
}
}
@@ -5024,6 +5068,82 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return (params.installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
}
+ @Override
+ public void setPreVerifiedDomains(@NonNull DomainSet preVerifiedDomains) {
+ // First check permissions
+ final boolean exemptFromPermissionChecks =
+ (mInstallerUid == Process.ROOT_UID) || (mInstallerUid == Process.SHELL_UID);
+ if (!exemptFromPermissionChecks) {
+ final Computer snapshot = mPm.snapshotComputer();
+ if (PackageManager.PERMISSION_GRANTED != snapshot.checkUidPermission(
+ Manifest.permission.ACCESS_INSTANT_APPS, mInstallerUid)) {
+ throw new SecurityException("You need android.permission.ACCESS_INSTANT_APPS "
+ + "permission to set pre-verified domains.");
+ }
+ ComponentName instantAppInstallerComponent = snapshot.getInstantAppInstallerComponent();
+ if (instantAppInstallerComponent == null) {
+ // Shouldn't happen
+ throw new IllegalStateException("Instant app installer is not available. "
+ + "Only the instant app installer can call this API.");
+ }
+ if (!instantAppInstallerComponent.getPackageName().equals(getInstallerPackageName())) {
+ throw new SecurityException("Only the instant app installer can call this API.");
+ }
+ }
+ // Then check size limits
+ final long preVerifiedDomainsCountLimit = getPreVerifiedDomainsCountLimit();
+ if (preVerifiedDomains.getDomains().size() > preVerifiedDomainsCountLimit) {
+ throw new IllegalArgumentException(
+ "The number of pre-verified domains have exceeded the maximum of "
+ + preVerifiedDomainsCountLimit);
+ }
+ final long preVerifiedDomainLengthLimit = getPreVerifiedDomainLengthLimit();
+ for (String domain : preVerifiedDomains.getDomains()) {
+ if (domain.length() > preVerifiedDomainLengthLimit) {
+ throw new IllegalArgumentException(
+ "Pre-verified domain: [" + domain + " ] exceeds maximum length allowed: "
+ + preVerifiedDomainLengthLimit);
+ }
+ }
+ // Okay to proceed
+ synchronized (mLock) {
+ mPreVerifiedDomains = preVerifiedDomains;
+ }
+ }
+
+ private static long getPreVerifiedDomainsCountLimit() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
+ DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static long getPreVerifiedDomainLengthLimit() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
+ DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ @Nullable
+ public DomainSet getPreVerifiedDomains() {
+ assertCallerIsOwnerOrRoot();
+ synchronized (mLock) {
+ assertPreparedAndNotCommittedOrDestroyedLocked("getPreVerifiedDomains");
+ return mPreVerifiedDomains;
+ }
+ }
+
+
void setSessionReady() {
synchronized (mLock) {
// Do not allow destroyed/failed session to change state
@@ -5250,6 +5370,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
pw.printPair("mSessionErrorCode", mSessionErrorCode);
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+ if (mPreVerifiedDomains != null) {
+ pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
+ }
pw.println();
pw.decreaseIndent();
@@ -5546,7 +5669,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeByteArrayAttribute(out, ATTR_SIGNATURE, signature);
out.endTag(null, TAG_SESSION_CHECKSUM_SIGNATURE);
}
-
+ if (mPreVerifiedDomains != null) {
+ for (String domain : mPreVerifiedDomains.getDomains()) {
+ out.startTag(null, TAG_PRE_VERIFIED_DOMAINS);
+ writeStringAttribute(out, ATTR_DOMAIN, domain);
+ out.endTag(null, TAG_PRE_VERIFIED_DOMAINS);
+ }
+ }
}
out.endTag(null, TAG_SESSION);
@@ -5673,6 +5802,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
List<InstallationFile> files = new ArrayList<>();
ArrayMap<String, List<Checksum>> checksums = new ArrayMap<>();
ArrayMap<String, byte[]> signatures = new ArrayMap<>();
+ ArraySet<String> preVerifiedDomainSet = new ArraySet<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -5726,6 +5856,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final byte[] signature = readByteArrayAttribute(in, ATTR_SIGNATURE);
signatures.put(fileName1, signature);
break;
+ case TAG_PRE_VERIFIED_DOMAINS:
+ preVerifiedDomainSet.add(readStringAttribute(in, ATTR_DOMAIN));
+ break;
}
}
@@ -5769,6 +5902,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ DomainSet preVerifiedDomains =
+ preVerifiedDomainSet.isEmpty() ? null : new DomainSet(preVerifiedDomainSet);
+
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName, installPackageUid,
updateOwnerPackageName, installerAttributionTag, params.packageSource);
@@ -5777,6 +5913,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
installerUid, installSource, params, createdMillis, committedMillis, stageDir,
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- sessionErrorCode, sessionErrorMessage);
+ sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 81f9d1b68438..e329f09b0c32 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -107,6 +107,7 @@ import android.system.Os;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.PrintWriterPrinter;
@@ -270,6 +271,10 @@ class PackageManagerShellCommand extends ShellCommand {
return runGetInstallLocation();
case "install-add-session":
return runInstallAddSession();
+ case "install-set-pre-verified-domains":
+ return runInstallSetPreVerifiedDomains();
+ case "install-get-pre-verified-domains":
+ return runInstallGetPreVerifiedDomains();
case "move-package":
return runMovePackage();
case "move-primary-storage":
@@ -1808,6 +1813,41 @@ class PackageManagerShellCommand extends ShellCommand {
true /*logSuccess*/);
}
+ private int runInstallSetPreVerifiedDomains() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int sessionId = Integer.parseInt(getNextArg());
+ final String preVerifiedDomainsStr = getNextArg();
+ final String[] preVerifiedDomains = preVerifiedDomainsStr.split(",");
+ PackageInstaller.Session session = null;
+ try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ session.setPreVerifiedDomains(new ArraySet<>(preVerifiedDomains));
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ return 0;
+ }
+
+ private int runInstallGetPreVerifiedDomains() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int sessionId = Integer.parseInt(getNextArg());
+ PackageInstaller.Session session = null;
+ try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ Set<String> preVerifiedDomains = session.getPreVerifiedDomains();
+ if (preVerifiedDomains.isEmpty()) {
+ pw.println("The session doesn't have any pre-verified domains specified.");
+ } else {
+ pw.println(String.join(",", preVerifiedDomains));
+ }
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ return 0;
+ }
+
private int runInstallRemove() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
@@ -4934,6 +4974,13 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs");
pw.println(" Add one or more session IDs to a multi-package session.");
pw.println("");
+ pw.println(" install-set-pre-verified-domains SESSION_ID PRE_VERIFIED_DOMAIN... ");
+ pw.println(" Specify a comma separated list of pre-verified domains for a session.");
+ pw.println("");
+ pw.println(" install-get-pre-verified-domains SESSION_ID");
+ pw.println(" List all the pre-verified domains that are specified in a session.");
+ pw.println(" The result list is comma separated.");
+ pw.println("");
pw.println(" install-commit SESSION_ID");
pw.println(" Commit the given active install session, installing the app.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 04e820534c6d..5575f52013d4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5044,6 +5044,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(prefix); pw.print(" updatableSystem=false");
pw.println();
}
+ if (pkg.getEmergencyInstaller() != null) {
+ pw.print(prefix); pw.print(" emergencyInstaller=");
+ pw.println(pkg.getEmergencyInstaller());
+ }
if (pkg.hasPreserveLegacyExternalStorage()) {
pw.print(prefix); pw.print(" hasPreserveLegacyExternalStorage=true");
pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index e9c6aabccbf2..b35f9c2cd242 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -209,9 +209,8 @@ class ShortcutUser {
if (ret == null) {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
- } else {
- ret.attemptToRestoreIfNeededAndSave();
}
+ ret.attemptToRestoreIfNeededAndSave();
return ret;
}
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index cc26c9b5f76c..9159851a5470 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -538,9 +538,10 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> {
} else {
if (fileInfo.mUserId != userId) {
// This should be impossible: private app files are always user-specific and
- // can't be accessed from different users.
- throw new IllegalArgumentException("Cannot change userId for '" + path
- + "' from " + fileInfo.mUserId + " to " + userId);
+ // can't be accessed from different users. But it does very occasionally happen
+ // (b/323665257). Ignore such cases - we shouldn't record data from a different
+ // user.
+ return false;
}
// Changing file type (i.e. loading the same file in different ways is possible if
// unlikely. We allow it but ignore it.
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 65466461c82e..b2e01c5f23f2 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -21,3 +21,10 @@ flag {
bug: "311793616"
is_fixed_read_only: true
}
+
+flag {
+ name: "streamlined_connectivity_battery_stats"
+ namespace: "backstage_power"
+ description: "Feature flag for streamlined connectivity battery stats"
+ bug: "323970018"
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b3983e731cf0..57448cb34a43 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9275,7 +9275,8 @@ public class WindowManagerService extends IWindowManager.Stub
return !fromWin.isFocused();
}
- private void moveFocusToActivity(@NonNull ActivityRecord activity) {
+ @VisibleForTesting
+ void moveFocusToActivity(@NonNull ActivityRecord activity) {
moveDisplayToTopInternal(activity.getDisplayId());
handleTaskFocusChange(activity.getTask(), activity);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5806c07c572..68dade0fae3b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3020,8 +3020,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- if (!isAnimating(TRANSITION | PARENTS)) {
+ // If a hide animation is applied, then let onAnimationFinished
+ // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false
+ // to commit invisible immediately.
+ if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) {
doAnimation = false;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 09c4f7ca3c6c..6428591d8b8d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,10 +499,6 @@ class WindowStateAnimator {
}
void applyEnterAnimationLocked() {
- if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
- // It's unnecessary to play enter animation below starting window.
- return;
- }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
@@ -513,8 +509,10 @@ class WindowStateAnimator {
// We don't apply animation for application main window here since this window type
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
- // WallpaperController should handle it.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
+ // WallpaperController should handle it. Also skip play enter animation for the window
+ // below starting window.
+ if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3dcf42d80d60..b6f7eb39e29b 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -103,14 +103,16 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
flattenedPrimaryProviders.add(cn.flattenToString());
}
+ final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
mRequestId, mClientRequest,
mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- /*defaultProviderId=*/flattenedPrimaryProviders),
- providerDataList, /*isRequestForAllOptions=*/ false);
+ /*defaultProviderId=*/flattenedPrimaryProviders,
+ isShowAllOptionsRequested),
+ providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 1a9a0e6888c0..9e362b3e4784 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -111,13 +111,15 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
}
cancelExistingPendingIntent();
+ final boolean isShowAllOptionsRequested = true;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
- Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+ Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
/*providerDataList=*/ null,
- /*isRequestForAllOptions=*/ true);
+ /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
for (ProviderData providerData : providerDataList) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index b33f531afdac..4068d7b5c95a 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -102,15 +102,17 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
Binder.withCleanCallingIdentity(() -> {
try {
cancelExistingPendingIntent();
+ final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext,
mClientAppInfo.getPackageName(),
Manifest.permission
- .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+ .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
providerDataList,
- /*isRequestForAllOptions=*/ false);
+ /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 30af56749e05..6b313fda1dd2 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -187,12 +187,14 @@ public class PrepareGetRequestSession extends GetRequestSession {
}
}
if (!providerDataList.isEmpty()) {
+ final boolean isShowAllOptionsRequested = false;
return mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
- Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
- providerDataList, /*isRequestForAllOptions=*/ false);
+ Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+ isShowAllOptionsRequested),
+ providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
} else {
return null;
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 50e426ca0282..68038fa87ae0 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -49,7 +49,6 @@ import com.android.server.wm.ActivityMetricsLaunchObserver;
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
-import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -314,7 +313,7 @@ public final class ProfcollectForwardingService extends SystemService {
Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
return;
}
- aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+ aml.setBatchDexoptStartCallback(Runnable::run,
(snapshot, reason, defaultPackages, builder, passedSignal) -> {
traceOnDex2oatStart();
});
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 811b0860ea64..7aa2ff50dbbd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -22,7 +22,9 @@ import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAUL
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainSet
import android.os.Parcel
+import android.os.Process
import android.platform.test.annotations.Presubmit
import android.util.AtomicFile
import android.util.Slog
@@ -173,7 +175,7 @@ class PackageInstallerSessionTest {
/* stagingManager */ null,
/* sessionId */ sessionId,
/* userId */ 456,
- /* installerUid */ -1,
+ /* installerUid */ Process.myUid(),
/* installSource */ installSource,
/* sessionParams */ params,
/* createdMillis */ 0L,
@@ -183,8 +185,8 @@ class PackageInstallerSessionTest {
/* files */ null,
/* checksums */ null,
/* prepared */ true,
- /* committed */ true,
- /* destroyed */ staged,
+ /* committed */ false,
+ /* destroyed */ false,
/* sealed */ false, // Setting to true would trigger some PM logic.
/* childSessionIds */ childSessionIds.toIntArray(),
/* parentSessionId */ parentSessionId,
@@ -192,7 +194,8 @@ class PackageInstallerSessionTest {
/* isFailed */ false,
/* isApplied */ false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
- /* stagedSessionErrorMessage */ "some error"
+ /* stagedSessionErrorMessage */ "some error",
+ /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
)
}
@@ -332,6 +335,7 @@ class PackageInstallerSessionTest {
assertThat(expected.parentSessionId).isEqualTo(actual.parentSessionId)
assertThat(expected.childSessionIds).asList()
.containsExactlyElementsIn(actual.childSessionIds.toList())
+ assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
}
private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index ef9c62fd3795..cfe701f42065 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -272,7 +272,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::hasPreserveLegacyExternalStorage,
AndroidPackage::hasRequestForegroundServiceExemption,
AndroidPackage::hasRequestRawExternalStorageAccess,
- AndroidPackage::isUpdatableSystem
+ AndroidPackage::isUpdatableSystem,
+ AndroidPackage::getEmergencyInstaller
)
override fun extraParams() = listOf(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index d2547a3ff336..6f9b8dfc023d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -755,7 +755,8 @@ public class StagingManagerTest {
/* isFailed */ false,
/* isApplied */false,
/* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
- /* stagedSessionErrorMessage */ "no error");
+ /* stagedSessionErrorMessage */ "no error",
+ /* preVerifiedDomains */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
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 408442bcceed..35ad55c0938e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -74,7 +74,9 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -83,6 +85,8 @@ import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.authorization.IKeystoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -100,6 +104,7 @@ import com.android.server.biometrics.sensors.LockoutTracker;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -110,6 +115,8 @@ import java.util.Random;
@Presubmit
@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
public class BiometricServiceTest {
@Rule
@@ -171,6 +178,8 @@ public class BiometricServiceTest {
private UserManager mUserManager;
@Mock
private BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
@Mock
private IKeystoreAuthorization mKeystoreAuthService;
@@ -235,6 +244,14 @@ public class BiometricServiceTest {
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(TestableLooper.get(this).getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(Looper.getMainLooper()));
+ }
+
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
"1:8:15", // ID1:Face:Strong
@@ -312,7 +329,7 @@ public class BiometricServiceTest {
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -333,7 +350,7 @@ public class BiometricServiceTest {
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -360,7 +377,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -377,7 +394,7 @@ public class BiometricServiceTest {
public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -451,7 +468,7 @@ public class BiometricServiceTest {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -1374,7 +1391,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
// Credential requested but not set up
@@ -1428,7 +1445,7 @@ public class BiometricServiceTest {
@Test
public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
// When only biometric is requested
@@ -1515,7 +1532,7 @@ public class BiometricServiceTest {
@Test
public void testRegisterAuthenticator_updatesStrengths() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
verify(mBiometricService.mBiometricStrengthController).startListening();
@@ -1533,7 +1550,7 @@ public class BiometricServiceTest {
@Test
public void testWithDowngradedAuthenticator() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
final int testId = 0;
@@ -1639,7 +1656,7 @@ public class BiometricServiceTest {
@Test(expected = IllegalStateException.class)
public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
@@ -1653,7 +1670,7 @@ public class BiometricServiceTest {
@Test(expected = IllegalArgumentException.class)
public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
@@ -1665,7 +1682,7 @@ public class BiometricServiceTest {
@Test
public void testRegistrationHappyPath_isOk() throws Exception {
// This is being tested in many of the other cases, but here's the base case.
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
for (String s : mInjector.getConfiguration(null)) {
@@ -1751,7 +1768,7 @@ public class BiometricServiceTest {
final IBiometricEnabledOnKeyguardCallback callback =
mock(IBiometricEnabledOnKeyguardCallback.class);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
@@ -1775,7 +1792,7 @@ public class BiometricServiceTest {
throws RemoteException {
mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
}
@@ -1799,7 +1816,7 @@ public class BiometricServiceTest {
when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
.thenReturn(expectedResult);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
@@ -1822,7 +1839,7 @@ public class BiometricServiceTest {
// TODO: Reconcile the registration strength with the injector
private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1855,7 +1872,7 @@ public class BiometricServiceTest {
// TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
// all tests.
private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1993,8 +2010,12 @@ public class BiometricServiceTest {
return requestWrapper.eligibleSensors.get(0).getCookie();
}
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ private void waitForIdle() {
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ TestableLooper.get(this).processAllMessages();
+ } else {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
}
private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 7648bd17f53c..9eca93e9f054 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,19 +24,28 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.HidlFaceSensorConfig;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
@@ -49,18 +58,23 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -92,6 +106,14 @@ public class FaceProviderTest {
private BiometricStateCallback mBiometricStateCallback;
@Mock
private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
+ @Mock
+ private Handler mBiometricCallbackHandler;
+ @Mock
+ private BiometricScheduler<IFace, ISession> mScheduler;
+ @Mock
+ AuthSessionCoordinator mAuthSessionCoordinator;
private final TestLooper mLooper = new TestLooper();
private SensorProps[] mSensorProps;
@@ -109,6 +131,16 @@ public class FaceProviderTest {
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
.thenReturn(FRR_THRESHOLD);
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ mBiometricCallbackHandler);
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ if (Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ mLooper.getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ Looper.getMainLooper()));
+ }
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -123,7 +155,7 @@ public class FaceProviderTest {
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
- mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
+ mBiometricContext, mDaemon, mBiometricHandlerProvider,
false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@@ -159,8 +191,7 @@ public class FaceProviderTest {
mFaceProvider = new FaceProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
- new Handler(mLooper.getLooper()),
- true /* resetLockoutRequiresChallenge */,
+ mBiometricHandlerProvider, true /* resetLockoutRequiresChallenge */,
true /* testHalEnabled */);
assertThat(mFaceProvider.mFaceSensors.get(faceId)
@@ -215,6 +246,54 @@ public class FaceProviderTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAuthenticateCallbackHandler() {
+ waitForIdle();
+
+ mFaceProvider.mFaceSensors.get(0).setScheduler(mScheduler);
+ mFaceProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()),
+ new FaceAuthenticateOptions.Builder()
+ .setSensorId(0)
+ .build(),
+ false /* restricted */, 1 /* statsClient */,
+ true /* allowBackgroundAuthentication */);
+
+ waitForIdle();
+
+ ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+ ClientMonitorCallback.class);
+ ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+ BaseClientMonitor.class);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+ Message.class);
+
+ verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+ callbackArgumentCaptor.capture());
+
+ BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+ ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+ callback.onClientStarted(client);
+
+ verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+ callback.onClientFinished(client, true /* success */);
+
+ verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+ messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+ anyBoolean());
+ }
+
private void waitForIdle() {
if (Flags.deHidl()) {
mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 258be573d005..0a35037762bd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,21 +24,31 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
@@ -50,11 +60,16 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -62,6 +77,7 @@ import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDisp
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -93,6 +109,14 @@ public class FingerprintProviderTest {
private BiometricStateCallback mBiometricStateCallback;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
+ @Mock
+ private Handler mBiometricCallbackHandler;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private BiometricScheduler<IFingerprint, ISession> mScheduler;
private final TestLooper mLooper = new TestLooper();
@@ -109,6 +133,16 @@ public class FingerprintProviderTest {
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ mBiometricCallbackHandler);
+ if (Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(mLooper.getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(Looper.getMainLooper()));
+ }
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -126,9 +160,8 @@ public class FingerprintProviderTest {
mFingerprintProvider = new FingerprintProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
- mDaemon, new Handler(mLooper.getLooper()),
- false /* resetLockoutRequiresHardwareAuthToken */,
- true /* testHalEnabled */);
+ mDaemon, mBiometricHandlerProvider,
+ false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */);
}
@Test
@@ -160,7 +193,7 @@ public class FingerprintProviderTest {
mBiometricStateCallback, mAuthenticationStateListeners,
hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
- new Handler(mLooper.getLooper()),
+ mBiometricHandlerProvider,
false /* resetLockoutRequiresHardwareAuthToken */,
true /* testHalEnabled */);
@@ -218,6 +251,56 @@ public class FingerprintProviderTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testScheduleAuthenticate() {
+ waitForIdle();
+
+ mFingerprintProvider.mFingerprintSensors.get(0).setScheduler(mScheduler);
+ mFingerprintProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()),
+ new FingerprintAuthenticateOptions.Builder()
+ .setSensorId(0)
+ .build(),
+ false /* restricted */, 1 /* statsClient */,
+ true /* allowBackgroundAuthentication */);
+
+ waitForIdle();
+
+ ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+ ClientMonitorCallback.class);
+ ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+ BaseClientMonitor.class);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+ Message.class);
+
+ verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+ callbackArgumentCaptor.capture());
+
+ BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+ ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+ callback.onClientStarted(client);
+
+ verify(mBiometricStateCallback).onClientStarted(eq(client));
+ verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+ callback.onClientFinished(client, true /* success */);
+
+ verify(mBiometricStateCallback).onClientFinished(eq(client), eq(true /* success */));
+ verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+ messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+ anyBoolean());
+ }
+
private void waitForIdle() {
if (Flags.deHidl()) {
mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index e075379284f0..c0ea1571d8c7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -106,13 +106,13 @@ public class PackageDynamicCodeLoadingTests {
}
@Test
- public void testRecord_changeUserForFile_throws() {
+ public void testRecord_changeUserForFile_ignored() {
Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
PackageDynamicCodeLoading info = makePackageDcl(entry1);
- assertThrows(() -> record(info, entry2));
+ assertThat(record(info, entry2)).isFalse();
assertHasEntries(info, entry1);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 516fb4aa40c6..5eb76e352ea2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,37 +15,56 @@
*/
package com.android.server.notification;
+import static android.app.Notification.COLOR_DEFAULT;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.server.notification.GroupHelper.BASE_FLAGS;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.app.Notification;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.server.UiServiceTestCase;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -59,23 +78,37 @@ import java.util.List;
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
@RunWith(AndroidJUnit4.class)
public class GroupHelperTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private @Mock GroupHelper.Callback mCallback;
+ private @Mock PackageManager mPackageManager;
private final static int AUTOGROUP_AT_COUNT = 7;
private GroupHelper mGroupHelper;
+ private @Mock Icon mSmallIcon;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mGroupHelper = new GroupHelper(AUTOGROUP_AT_COUNT, mCallback);
+ mGroupHelper = new GroupHelper(getContext(), mPackageManager, AUTOGROUP_AT_COUNT,
+ mCallback);
+
+ NotificationRecord r = mock(NotificationRecord.class);
+ StatusBarNotification sbn = getSbn("package", 0, "0", UserHandle.SYSTEM);
+ when(r.getNotification()).thenReturn(sbn.getNotification());
+ when(r.getSbn()).thenReturn(sbn);
+ when(mSmallIcon.sameAs(mSmallIcon)).thenReturn(true);
}
private StatusBarNotification getSbn(String pkg, int id, String tag,
- UserHandle user, String groupKey) {
+ UserHandle user, String groupKey, Icon smallIcon, int iconColor) {
Notification.Builder nb = new Notification.Builder(getContext(), "test_channel_id")
.setContentTitle("A")
- .setWhen(1205);
+ .setWhen(1205)
+ .setSmallIcon(smallIcon)
+ .setColor(iconColor);
if (groupKey != null) {
nb.setGroup(groupKey);
}
@@ -84,23 +117,32 @@ public class GroupHelperTest extends UiServiceTestCase {
}
private StatusBarNotification getSbn(String pkg, int id, String tag,
+ UserHandle user, String groupKey) {
+ return getSbn(pkg, id, tag, user, groupKey, mSmallIcon, Notification.COLOR_DEFAULT);
+ }
+
+ private StatusBarNotification getSbn(String pkg, int id, String tag,
UserHandle user) {
return getSbn(pkg, id, tag, user, null);
}
+ private NotificationAttributes getNotificationAttributes(int flags) {
+ return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT);
+ }
+
@Test
public void testGetAutogroupSummaryFlags_noChildren() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
}
@Test
public void testGetAutogroupSummaryFlags_oneOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -108,10 +150,10 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_NO_CLEAR));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -119,10 +161,10 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_BUBBLE));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -130,11 +172,11 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_multipleOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_ONGOING_EVENT);
- children.put("c", FLAG_BUBBLE);
- children.put("d", FLAG_ONGOING_EVENT);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
+ children.put("d", getNotificationAttributes(FLAG_ONGOING_EVENT));
assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -142,10 +184,10 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_oneAutoCancel() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", 0);
- children.put("b", FLAG_AUTO_CANCEL);
- children.put("c", FLAG_BUBBLE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(0));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("c", getNotificationAttributes(FLAG_BUBBLE));
assertEquals(BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -153,11 +195,11 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_allAutoCancel() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", FLAG_AUTO_CANCEL);
- children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
- children.put("c", FLAG_AUTO_CANCEL);
- children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+ children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("d", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE));
assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -165,11 +207,12 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
- ArrayMap<String, Integer> children = new ArrayMap<>();
- children.put("a", FLAG_AUTO_CANCEL);
- children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
- children.put("c", FLAG_AUTO_CANCEL);
- children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+ ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+ children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+ children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+ children.put("d", getNotificationAttributes(
+ FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT));
assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
mGroupHelper.getAutogroupSummaryFlags(children));
@@ -230,11 +273,11 @@ public class GroupHelperTest extends UiServiceTestCase {
getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -248,11 +291,11 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -266,11 +309,11 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -282,11 +325,11 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -301,11 +344,11 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
- eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -329,8 +372,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should keep FLAG_ONGOING_EVENT if any child has it
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -355,7 +398,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -378,8 +422,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -403,8 +447,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, true);
// Summary is now ongoing
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
}
@Test
@@ -430,7 +474,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, true);
// Summary is no longer ongoing
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -455,7 +500,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationRemoved(notifications.get(1));
// Summary is still ongoing
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -479,7 +524,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should no longer be autocancelable
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
}
@Test
@@ -505,8 +551,8 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(notifications.get(0), true);
// Summary should now autocancelable
- verify(mCallback).updateAutogroupSummary(
- anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
}
@Test
@@ -530,7 +576,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, true);
// Summary should be still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -552,7 +598,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationRemoved(notifications.get(0));
// Summary should still be autocancelable
- verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
}
@Test
@@ -565,7 +611,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -593,7 +639,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -622,7 +668,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -646,8 +692,213 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
- verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
- verify(mCallback, never()).addAutoGroupSummary(
- anyInt(), anyString(), anyString(), anyInt());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+ eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_sameIcon_sameColor() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+ final int iconColor = Color.BLUE;
+ final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(attr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with the same color
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Check that the summary was updated
+ //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAddSummary_diffIcon_diffColor() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+
+ // Spy GroupHelper for getMonochromeAppIcon
+ final Icon monochromeIcon = mock(Icon.class);
+ when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true);
+ GroupHelper groupHelper = spy(mGroupHelper);
+ doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+ final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
+ initialIcon, initialIconColor);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor);
+ groupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(initialAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with a different color
+ final Icon newIcon = mock(Icon.class);
+ final int newIconColor = Color.YELLOW;
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+ newIconColor);
+ groupHelper.onNotificationPosted(sbn, true);
+
+ // Summary should be updated to the default color and the icon to the monochrome icon
+ NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
+ COLOR_DEFAULT);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+ final NotificationAttributes initialAttr = new NotificationAttributes(
+ GroupHelper.FLAG_INVALID, initialIcon, initialIconColor);
+
+ // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor);
+ notifications.add(sbn);
+ }
+ // And an additional notification with different icon and color
+ final int lastIdx = AUTOGROUP_AT_COUNT - 1;
+ StatusBarNotification newSbn = getSbn(pkg, lastIdx,
+ String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
+ Color.YELLOW);
+ notifications.add(newSbn);
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+
+ // Remove last notification (the only one with different icon and color)
+ mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+
+ // Summary should be updated to the common icon and color
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(initialAttr));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIcon_sameIcon() {
+ final String pkg = "package";
+ final Icon icon = mock(Icon.class);
+ when(icon.sameAs(icon)).thenReturn(true);
+
+ // Create notifications with the same icon
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT));
+ }
+
+ //Check that the generated summary icon is the same as the child notifications'
+ Icon summaryIcon = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+ assertThat(summaryIcon).isEqualTo(icon);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIcon_diffIcon() {
+ final String pkg = "package";
+ // Spy GroupHelper for getMonochromeAppIcon
+ final Icon monochromeIcon = mock(Icon.class);
+ GroupHelper groupHelper = spy(mGroupHelper);
+ doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+ // Create notifications with different icons
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT));
+ }
+
+ // Check that the generated summary icon is the monochrome icon
+ Icon summaryIcon = groupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+ assertThat(summaryIcon).isEqualTo(monochromeIcon);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIconColor_sameColor() {
+ final String pkg = "package";
+ final int iconColor = Color.BLUE;
+ // Create notifications with the same icon color
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor));
+ }
+
+ // Check that the generated summary icon color is the same as the child notifications'
+ int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+ childrenAttr).iconColor;
+ assertThat(summaryIconColor).isEqualTo(iconColor);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testAutobundledSummaryIconColor_diffColor() {
+ final String pkg = "package";
+ // Create notifications with different icon colors
+ List<NotificationAttributes> childrenAttr = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i));
+ }
+
+ // Check that the generated summary icon color is the default color
+ int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+ childrenAttr).iconColor;
+ assertThat(summaryIconColor).isEqualTo(Notification.COLOR_DEFAULT);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testMonochromeAppIcon_adaptiveIconExists() throws Exception {
+ final String pkg = "testPackage";
+ final int monochromeIconResId = 1234;
+ AdaptiveIconDrawable adaptiveIcon = mock(AdaptiveIconDrawable.class);
+ Drawable monochromeIcon = mock(Drawable.class);
+ when(mPackageManager.getApplicationIcon(pkg)).thenReturn(adaptiveIcon);
+ when(adaptiveIcon.getMonochrome()).thenReturn(monochromeIcon);
+ when(adaptiveIcon.getSourceDrawableResId()).thenReturn(monochromeIconResId);
+ assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+ .isEqualTo(monochromeIconResId);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+ public void testMonochromeAppIcon_adaptiveIconMissing_fallback() throws Exception {
+ final String pkg = "testPackage";
+ final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+ when(mPackageManager.getApplicationIcon(pkg)).thenReturn(mock(Drawable.class));
+ assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+ .isEqualTo(fallbackIconResId);
}
}
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 8261dee0340f..f6cf4da84b3f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -279,6 +279,7 @@ import com.android.server.UiServiceTestCase;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.notification.NotificationManagerService.PostNotificationTracker;
@@ -658,7 +659,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
// flags here.
mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
- Flags.FLAG_POLITE_NOTIFICATIONS);
+ Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+
initNMS();
}
@@ -2332,8 +2334,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mAutobundledSummaries.put(0, new ArrayMap<>());
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
- mService.updateAutobundledSummaryFlags(
- 0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
+ mService.updateAutobundledSummaryLocked(0, "pkg",
+ new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+ mock(Icon.class), 0), false);
waitForIdle();
assertTrue(summary.getSbn().isOngoing());
@@ -2350,7 +2353,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
mService.mSummaryByGroupKey.put("pkg", summary);
- mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
+ mService.updateAutobundledSummaryLocked(0, "pkg",
+ new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0), false);
waitForIdle();
assertFalse(summary.getSbn().isOngoing());
@@ -3427,8 +3432,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
- NotificationRecord r = mService.createAutoGroupSummary(
- temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
+ NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(),
+ temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0);
assertThat(r.isImportanceFixed()).isTrue();
}
@@ -4116,7 +4121,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ r.getKey(), 1000, null);
verify(mWorkerHandler, never()).post(
any(NotificationManagerService.SnoozeNotificationRunnable.class));
@@ -4134,10 +4140,115 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
- mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ r2.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_new() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create recent notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis());
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
verify(mWorkerHandler).post(
any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is logged.
+ verify(mAppOpsManager).noteOpNoThrow(
+ AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null,
+ null);
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_old() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create old notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis() - 60000);
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_new_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create recent notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis());
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_old_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create old notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis() - 60000);
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
}
@Test
@@ -9741,8 +9852,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
throws RemoteException {
IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
- // Set up volume to be above 0 for the sound to actually play
+ // Set up volume to be above 0, and for AudioManager to signal playback should happen,
+ // for the sound to actually play
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+ when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
+ .thenReturn(true);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
@@ -11962,7 +12076,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// add summary
mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
nr1.getSbn().getPackageName(), nr1.getKey(),
- GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
+ GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0));
// cancel both children
mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -11989,8 +12103,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// add notifications + summary for USER_SYSTEM
mService.addNotification(nr0);
mService.addNotification(nr1);
- mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
- nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+ nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
// add notifications + summary for USER_ALL
NotificationRecord nr0_all =
@@ -12000,8 +12115,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(nr0_all);
mService.addNotification(nr1_all);
- mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
- nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+ mService.addNotification(
+ mService.createAutoGroupSummary(nr0_all.getUserId(),
+ nr0_all.getSbn().getPackageName(),
+ nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
// cancel both children for USER_ALL
mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c44be7b9db51..c29547f123aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,7 +56,6 @@ import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
@@ -632,8 +631,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
public void testAdjacentFocusInActivityEmbedding() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
Task task = createTask(mDefaultDisplay);
TaskFragment primary = createTaskFragmentWithActivity(task);
TaskFragment secondary = createTaskFragmentWithActivity(task);
@@ -645,7 +644,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
doReturn(primary).when(windowState).getTaskFragment();
startBackNavigation();
- verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+ verify(mWm).moveFocusToActivity(any());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a0562aa2f710..f02dd3f70feb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -236,6 +236,26 @@ public class WindowStateTests extends WindowTestsBase {
assertTrue(window.isOnScreen());
window.hide(false /* doAnimation */, false /* requestAnim */);
assertFalse(window.isOnScreen());
+
+ // Verifies that a window without animation can be hidden even if its parent is animating.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ assertTrue(window.isVisibleByPolicy());
+ window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class),
+ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM);
+ window.mAttrs.windowAnimations = 0;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertFalse(window.isVisibleByPolicy());
+ assertFalse(window.isOnScreen());
+
+ // Verifies that a window with animation can be hidden after the hide animation is finished.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ window.mAttrs.windowAnimations = android.R.style.Animation_Dialog;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertTrue(window.isVisibleByPolicy());
+ window.cancelAnimation();
+ assertFalse(window.isVisibleByPolicy());
}
@Test
diff --git a/services/usage/Android.bp b/services/usage/Android.bp
index 867773da7ba9..2c1095a34cfb 100644
--- a/services/usage/Android.bp
+++ b/services/usage/Android.bp
@@ -18,5 +18,8 @@ java_library_static {
name: "services.usage",
defaults: ["platform_service_defaults"],
srcs: [":services.usage-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "service-art.stubs.system_server",
+ ],
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 2445f51a4241..44f4068fb424 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -19,7 +19,9 @@ package com.android.server.usage;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.ArrayUtils.defeatNullable;
+import static com.android.server.pm.DexOptHelper.getArtManagerLocal;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
import android.Manifest;
@@ -76,6 +78,9 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtManagedFileStats;
+import com.android.server.pm.PackageManagerLocal.FilteredSnapshot;
import com.android.server.IoThread;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -449,7 +454,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
}
if (Flags.getAppBytesByDataTypeApi()) {
computeAppStatsByDataTypes(
- stats, appInfo.sourceDir);
+ stats, appInfo.sourceDir, packageNames[i]);
}
}
} catch (NameNotFoundException e) {
@@ -592,6 +597,9 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+ res.dexoptBytes = stats.dexoptSize;
+ res.curProfBytes = stats.curProfSize;
+ res.refProfBytes = stats.refProfSize;
res.apkBytes = stats.apkSize;
res.libBytes = stats.libSize;
res.dmBytes = stats.dmSize;
@@ -946,7 +954,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
}
private void computeAppStatsByDataTypes(
- PackageStats stats, String sourceDirName) {
+ PackageStats stats, String sourceDirName, String packageName) {
// Get apk, lib, dm file sizes.
File srcDir = new File(sourceDirName);
@@ -958,5 +966,24 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
stats.apkSize += getFileBytesInDir(srcDir, ".apk");
stats.dmSize += getFileBytesInDir(srcDir, ".dm");
stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+
+ // Get dexopt, current profle and reference profile sizes.
+ if (SystemProperties.getBoolean("dalvik.vm.features.art_managed_file_stats", false)) {
+ ArtManagedFileStats artManagedFileStats;
+ try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
+ artManagedFileStats =
+ getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
+ }
+
+ stats.dexoptSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
+ stats.refProfSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
+ stats.curProfSize +=
+ artManagedFileStats
+ .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
+ }
}
}