summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java44
-rw-r--r--api/Android.bp1
-rw-r--r--boot/Android.bp4
-rw-r--r--core/api/current.txt15
-rw-r--r--core/api/system-current.txt70
-rw-r--r--core/java/android/app/ActivityManager.java4
-rw-r--r--core/java/android/app/BroadcastOptions.java33
-rw-r--r--core/java/android/app/SystemServiceRegistry.java2
-rw-r--r--core/java/android/app/time/TimeCapabilities.java2
-rw-r--r--core/java/android/app/time/TimeCapabilitiesAndConfig.java2
-rw-r--r--core/java/android/app/time/TimeConfiguration.java3
-rw-r--r--core/java/android/app/time/TimeManager.java36
-rw-r--r--core/java/android/app/time/TimeState.java2
-rw-r--r--core/java/android/app/time/TimeZoneCapabilities.java2
-rw-r--r--core/java/android/app/time/TimeZoneState.java2
-rw-r--r--core/java/android/app/time/UnixEpochTime.java5
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java16
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/pm/PackageManager.java8
-rw-r--r--core/java/android/credentials/ui/BaseDialogResult.java123
-rw-r--r--core/java/android/credentials/ui/Constants.java1
-rw-r--r--core/java/android/credentials/ui/Entry.java37
-rw-r--r--core/java/android/credentials/ui/IntentFactory.java3
-rw-r--r--core/java/android/credentials/ui/ProviderDialogResult.java100
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java91
-rw-r--r--core/java/android/credentials/ui/UserSelectionDialogResult.java (renamed from core/java/android/credentials/ui/UserSelectionResult.java)66
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java44
-rw-r--r--core/java/android/hardware/face/FaceManager.java14
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl5
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java16
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl5
-rw-r--r--core/java/android/hardware/radio/Announcement.java6
-rw-r--r--core/java/android/hardware/radio/ProgramList.java7
-rw-r--r--core/java/android/os/storage/OWNERS16
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/credentials/Action.java9
-rw-r--r--core/java/android/service/credentials/CreateCredentialRequest.java4
-rw-r--r--core/java/android/service/credentials/CreateCredentialResponse.java4
-rw-r--r--core/java/android/service/credentials/CredentialEntry.java16
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfo.java184
-rw-r--r--core/java/android/service/credentials/CredentialsDisplayContent.java12
-rw-r--r--core/java/android/service/credentials/GetCredentialsRequest.java8
-rw-r--r--core/java/android/service/credentials/GetCredentialsResponse.java9
-rw-r--r--core/java/android/service/credentials/SaveEntry.java13
-rw-r--r--core/java/android/service/dreams/DreamService.java2
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java8
-rw-r--r--core/java/android/service/timezone/TimeZoneProviderEvent.java104
-rw-r--r--core/java/android/service/timezone/TimeZoneProviderService.java33
-rw-r--r--core/java/android/service/timezone/TimeZoneProviderStatus.aidl22
-rw-r--r--core/java/android/service/timezone/TimeZoneProviderStatus.java336
-rw-r--r--core/java/android/text/Layout.java12
-rw-r--r--core/java/android/view/KeyEvent.java1
-rw-r--r--core/java/android/view/ViewRootImpl.java26
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java34
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java2
-rw-r--r--core/java/android/widget/TextView.java53
-rw-r--r--core/java/android/window/BackEvent.java22
-rw-r--r--core/java/android/window/BackProgressAnimator.java139
-rw-r--r--core/java/android/window/IOnBackInvokedCallback.aidl13
-rw-r--r--core/java/android/window/OnBackAnimationCallback.java11
-rw-r--r--core/java/android/window/OnBackInvokedCallback.java28
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java13
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java15
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl1
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java8
-rw-r--r--core/java/com/android/internal/policy/PhoneFallbackEventHandler.java2
-rw-r--r--core/java/com/android/internal/policy/TransitionAnimation.java92
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl3
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp16
-rw-r--r--core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp25
-rw-r--r--core/jni/fd_utils.cpp1
-rw-r--r--core/proto/android/view/imefocuscontroller.proto4
-rw-r--r--core/proto/android/view/inputmethod/inputmethodmanager.proto2
-rw-r--r--core/res/AndroidManifest.xml66
-rw-r--r--core/res/res/anim/dream_activity_close_exit.xml2
-rw-r--r--core/res/res/anim/dream_activity_open_enter.xml2
-rw-r--r--core/res/res/anim/dream_activity_open_exit.xml2
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java86
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java158
-rw-r--r--core/tests/coretests/src/android/app/time/TimeConfigurationTest.java56
-rw-r--r--core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java45
-rw-r--r--core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java102
-rw-r--r--core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java110
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java18
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java125
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java79
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java136
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java58
-rw-r--r--media/java/android/media/AudioManager.java4
-rw-r--r--media/java/android/media/ImageWriter.java7
-rw-r--r--media/java/android/media/midi/MidiManager.java3
-rw-r--r--packages/CarrierDefaultApp/Android.bp2
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml1
-rw-r--r--packages/CompanionDeviceManager/TEST_MAPPING12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt42
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt15
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt81
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt40
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt46
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt63
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt68
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt73
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt134
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt171
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt59
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt98
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt74
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminController.java79
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java10
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminControllerTest.java97
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java14
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java3
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt136
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt14
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt107
-rw-r--r--packages/SystemUI/checks/Android.bp4
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt6
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt214
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt17
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt33
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt45
-rw-r--r--packages/SystemUI/ktfmt_includes.txt1
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt13
-rw-r--r--packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml20
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_password_view.xml24
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml102
-rw-r--r--packages/SystemUI/res/layout/auth_credential_password_view.xml24
-rw-r--r--packages/SystemUI/res/layout/auth_credential_pattern_view.xml119
-rw-r--r--packages/SystemUI/res/layout/chipbar.xml12
-rw-r--r--packages/SystemUI/res/values-land/styles.xml31
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/styles.xml47
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/styles.xml44
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/styles.xml48
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/styles.xml44
-rw-r--r--packages/SystemUI/res/values/attrs.xml13
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml77
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt10
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java45
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt98
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java (renamed from packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java)9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java45
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dumpable.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt326
-rw-r--r--packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java378
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java131
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java121
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java153
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/sysui.proto27
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java365
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt356
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt169
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt145
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt212
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt293
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Condition.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto26
-rw-r--r--packages/SystemUI/tests/res/layout/custom_view_dark.xml1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt155
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java173
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java159
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt352
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt)70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt116
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt556
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt2
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java27
-rw-r--r--services/companion/TEST_MAPPING8
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java23
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java50
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java55
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java259
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java37
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java19
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java27
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java33
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java58
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java65
-rw-r--r--services/core/java/com/android/server/dreams/DreamController.java195
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java2
-rw-r--r--services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java279
-rw-r--r--services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java60
-rw-r--r--services/core/java/com/android/server/infra/ServiceNameBaseResolver.java325
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java313
-rw-r--r--services/core/java/com/android/server/pm/Computer.java6
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java33
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java43
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java33
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java51
-rw-r--r--services/core/java/com/android/server/power/SystemPropertiesWrapper.java15
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java19
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java34
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java12
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java8
-rw-r--r--services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java96
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java3
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java21
-rw-r--r--services/java/com/android/server/SystemServer.java3
-rw-r--r--services/tests/mockingservicestests/OWNERS3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java204
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java112
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java104
-rw-r--r--services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java160
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java67
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt196
-rw-r--r--services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java15
-rw-r--r--services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java62
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java2
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java37
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java76
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java81
-rwxr-xr-xtelephony/java/com/android/internal/telephony/ISub.aidl25
-rw-r--r--tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java1
468 files changed, 15106 insertions, 4635 deletions
diff --git a/Android.bp b/Android.bp
index aa654865e09b..ed7a4813efe6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -150,6 +150,9 @@ java_library {
visibility: [
// DO NOT ADD ANY MORE ENTRIES TO THIS LIST
"//external/robolectric-shadows:__subpackages__",
+ //This will eventually replace the item above, and serves the
+ //same purpose.
+ "//external/robolectric:__subpackages__",
"//frameworks/layoutlib:__subpackages__",
],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index bdd1fc548af2..048f4a48ab52 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -40,6 +40,8 @@ import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -58,6 +60,7 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryStatsInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.LimitExceededException;
import android.os.Looper;
@@ -166,6 +169,14 @@ public class JobSchedulerService extends com.android.server.SystemService
/** The number of the most recently completed jobs to keep track of for debugging purposes. */
private static final int NUM_COMPLETED_JOB_HISTORY = 20;
+ /**
+ * Require the hosting job to specify a network constraint if the included
+ * {@link android.app.job.JobWorkItem} indicates network usage.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
+
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -3147,7 +3158,11 @@ public class JobSchedulerService extends com.android.server.SystemService
return canPersist;
}
- private void validateJobFlags(JobInfo job, int callingUid) {
+ private void validateJob(JobInfo job, int callingUid) {
+ validateJob(job, callingUid, null);
+ }
+
+ private void validateJob(JobInfo job, int callingUid, @Nullable JobWorkItem jobWorkItem) {
job.enforceValidity(
CompatChanges.isChangeEnabled(
JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid));
@@ -3164,6 +3179,26 @@ public class JobSchedulerService extends com.android.server.SystemService
+ " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
}
}
+ if (jobWorkItem != null) {
+ jobWorkItem.enforceValidity();
+ if (jobWorkItem.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN
+ || jobWorkItem.getEstimatedNetworkUploadBytes()
+ != JobInfo.NETWORK_BYTES_UNKNOWN
+ || jobWorkItem.getMinimumNetworkChunkBytes()
+ != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ if (job.getRequiredNetwork() == null) {
+ final String errorMsg = "JobWorkItem implies network usage"
+ + " but job doesn't specify a network constraint";
+ if (CompatChanges.isChangeEnabled(
+ REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS,
+ callingUid)) {
+ throw new IllegalArgumentException(errorMsg);
+ } else {
+ Slog.e(TAG, errorMsg);
+ }
+ }
+ }
+ }
}
// IJobScheduler implementation
@@ -3184,7 +3219,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- validateJobFlags(job, uid);
+ validateJob(job, uid);
final long ident = Binder.clearCallingIdentity();
try {
@@ -3212,8 +3247,7 @@ public class JobSchedulerService extends com.android.server.SystemService
throw new NullPointerException("work is null");
}
- work.enforceValidity();
- validateJobFlags(job, uid);
+ validateJob(job, uid, work);
final long ident = Binder.clearCallingIdentity();
try {
@@ -3244,7 +3278,7 @@ public class JobSchedulerService extends com.android.server.SystemService
+ " not permitted to schedule jobs for other apps");
}
- validateJobFlags(job, callerUid);
+ validateJob(job, callerUid);
final long ident = Binder.clearCallingIdentity();
try {
diff --git a/api/Android.bp b/api/Android.bp
index 9306671d758c..a3e64a565422 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -98,6 +98,7 @@ combined_apis {
"framework-configinfrastructure",
"framework-connectivity",
"framework-connectivity-t",
+ "framework-devicelock",
"framework-federatedcompute",
"framework-graphics",
"framework-healthconnect",
diff --git a/boot/Android.bp b/boot/Android.bp
index 9fdb9bc6506a..7839918d6a54 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -72,6 +72,10 @@ platform_bootclasspath {
module: "com.android.conscrypt-bootclasspath-fragment",
},
{
+ apex: "com.android.devicelock",
+ module: "com.android.devicelock-bootclasspath-fragment",
+ },
+ {
apex: "com.android.federatedcompute",
module: "com.android.federatedcompute-bootclasspath-fragment",
},
diff --git a/core/api/current.txt b/core/api/current.txt
index 1bfd1e7b50cd..94d199cc00b6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34,6 +34,7 @@ package android {
field public static final String BIND_COMPANION_DEVICE_SERVICE = "android.permission.BIND_COMPANION_DEVICE_SERVICE";
field public static final String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
field public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
+ field public static final String BIND_CREDENTIAL_PROVIDER_SERVICE = "android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE";
field public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
field public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
@@ -106,6 +107,7 @@ package android {
field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
+ field public static final String MANAGE_DEVICE_LOCK_STATE = "android.permission.MANAGE_DEVICE_LOCK_STATE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
@@ -9844,6 +9846,7 @@ package android.content {
field public static final int CONTEXT_RESTRICTED = 4; // 0x4
field public static final String CREDENTIAL_SERVICE = "credential";
field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+ field public static final String DEVICE_LOCK_SERVICE = "device_lock";
field public static final String DEVICE_POLICY_SERVICE = "device_policy";
field public static final String DISPLAY_HASH_SERVICE = "display_hash";
field public static final String DISPLAY_SERVICE = "display";
@@ -11990,6 +11993,7 @@ package android.content.pm {
field public static final String FEATURE_CONTROLS = "android.software.controls";
field public static final String FEATURE_CREDENTIALS = "android.software.credentials";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+ field public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock";
field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
field public static final String FEATURE_EXPANDED_PICTURE_IN_PICTURE = "android.software.expanded_picture_in_picture";
@@ -39319,6 +39323,7 @@ package android.service.notification {
method public final void setNotificationsShown(String[]);
method public final void snoozeNotification(String, long);
method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+ field public static final String ACTION_SETTINGS_HOME = "android.service.notification.action.SETTINGS_HOME";
field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
@@ -39326,7 +39331,6 @@ package android.service.notification {
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
- field public static final String INTENT_CATEGORY_SETTINGS_HOME = "android.service.notification.category.SETTINGS_HOME";
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
@@ -41726,10 +41730,12 @@ package android.telephony {
field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+ field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING = "premium_capability_purchase_url_string";
+ field public static final String KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL = "premium_capability_supported_on_lte_bool";
field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -44008,7 +44014,7 @@ package android.telephony {
field public static final int PHONE_TYPE_GSM = 1; // 0x1
field public static final int PHONE_TYPE_NONE = 0; // 0x0
field public static final int PHONE_TYPE_SIP = 3; // 0x3
- field public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1; // 0x1
+ field public static final int PREMIUM_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4; // 0x4
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7
@@ -44016,12 +44022,13 @@ package android.telephony {
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 5; // 0x5
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
@@ -45403,7 +45410,7 @@ package android.text {
method public final int getParagraphLeft(int);
method public final int getParagraphRight(int);
method public float getPrimaryHorizontal(int);
- method @Nullable public android.util.Range<java.lang.Integer> getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
+ method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
method public float getSecondaryHorizontal(int);
method public void getSelectionPath(int, int, android.graphics.Path);
method public final float getSpacingAdd();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c170f74b41c3..755e1037ea6b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -145,6 +145,7 @@ package android {
field public static final String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL";
field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
+ field public static final String KILL_ALL_BACKGROUND_PROCESSES = "android.permission.KILL_ALL_BACKGROUND_PROCESSES";
field public static final String KILL_UID = "android.permission.KILL_UID";
field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
@@ -189,6 +190,7 @@ package android {
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final String MANAGE_SPEECH_RECOGNITION = "android.permission.MANAGE_SPEECH_RECOGNITION";
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
+ field public static final String MANAGE_SUBSCRIPTION_USER_ASSOCIATION = "android.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION";
field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION";
field public static final String MANAGE_UI_TRANSLATION = "android.permission.MANAGE_UI_TRANSLATION";
@@ -2502,11 +2504,49 @@ package android.app.time {
field @NonNull public static final android.os.Parcelable.Creator<android.app.time.ExternalTimeSuggestion> CREATOR;
}
+ public final class TimeCapabilities implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConfigureAutoDetectionEnabledCapability();
+ method public int getSetManualTimeCapability();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeCapabilities> CREATOR;
+ }
+
+ public final class TimeCapabilitiesAndConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.app.time.TimeCapabilities getCapabilities();
+ method @NonNull public android.app.time.TimeConfiguration getConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeCapabilitiesAndConfig> CREATOR;
+ }
+
+ public final class TimeConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isAutoDetectionEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeConfiguration> CREATOR;
+ }
+
+ public static final class TimeConfiguration.Builder {
+ ctor public TimeConfiguration.Builder();
+ ctor public TimeConfiguration.Builder(@NonNull android.app.time.TimeConfiguration);
+ method @NonNull public android.app.time.TimeConfiguration build();
+ method @NonNull public android.app.time.TimeConfiguration.Builder setAutoDetectionEnabled(boolean);
+ }
+
public final class TimeManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTime(@NonNull android.app.time.UnixEpochTime);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTimeZone(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeState getTimeState();
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneState getTimeZoneState();
method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void removeTimeZoneDetectorListener(@NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTime(@NonNull android.app.time.UnixEpochTime);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTimeZone(@NonNull String);
method @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME) public void suggestExternalTime(@NonNull android.app.time.ExternalTimeSuggestion);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeConfiguration(@NonNull android.app.time.TimeConfiguration);
method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeZoneConfiguration(@NonNull android.app.time.TimeZoneConfiguration);
}
@@ -2514,10 +2554,19 @@ package android.app.time {
method public void onChange();
}
+ public final class TimeState implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.app.time.UnixEpochTime getUnixEpochTime();
+ method public boolean getUserShouldConfirmTime();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeState> CREATOR;
+ }
+
public final class TimeZoneCapabilities implements android.os.Parcelable {
method public int describeContents();
method public int getConfigureAutoDetectionEnabledCapability();
method public int getConfigureGeoDetectionEnabledCapability();
+ method public int getSetManualTimeZoneCapability();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR;
}
@@ -2546,6 +2595,24 @@ package android.app.time {
method @NonNull public android.app.time.TimeZoneConfiguration.Builder setGeoDetectionEnabled(boolean);
}
+ public final class TimeZoneState implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getId();
+ method public boolean getUserShouldConfirmId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneState> CREATOR;
+ }
+
+ public final class UnixEpochTime implements android.os.Parcelable {
+ ctor public UnixEpochTime(long, long);
+ method @NonNull public android.app.time.UnixEpochTime at(long);
+ method public int describeContents();
+ method public long getElapsedRealtimeMillis();
+ method public long getUnixEpochTimeMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.UnixEpochTime> CREATOR;
+ }
+
}
package android.app.usage {
@@ -13324,6 +13391,7 @@ package android.telephony {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getUserHandle(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -13334,6 +13402,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setUserHandle(int, @Nullable android.os.UserHandle);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri CROSS_SIM_ENABLED_CONTENT_URI;
@@ -13665,6 +13734,7 @@ package android.telephony {
field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
field public static final int KEY_TYPE_EPDG = 1; // 0x1
field public static final int KEY_TYPE_WLAN = 2; // 0x2
+ field public static final int MOBILE_DATA_POLICY_AUTO_DATA_SWITCH = 3; // 0x3
field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index cb7b478d73b4..74329a38057f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3953,6 +3953,10 @@ public class ActivityManager {
* processes to reclaim memory; the system will take care of restarting
* these processes in the future as needed.
*
+ * <p class="note">On devices with a {@link Build.VERSION#SECURITY_PATCH} of 2022-12-01 or
+ * greater, third party applications can only use this API to kill their own processes.
+ * </p>
+ *
* @param packageName The name of the package whose processes are to
* be killed.
*/
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 13da1901e559..cc4650a7df71 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -62,6 +62,7 @@ public class BroadcastOptions extends ComponentOptions {
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
+ private boolean mIsInteractiveBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -168,6 +169,13 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.is_alarm";
/**
+ * Corresponds to {@link #setInteractiveBroadcast(boolean)}
+ * @hide
+ */
+ public static final String KEY_INTERACTIVE_BROADCAST =
+ "android:broadcast.is_interactive";
+
+ /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -281,6 +289,7 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+ mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -599,6 +608,27 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * When set, this broadcast will be understood as having originated from
+ * some direct interaction by the user such as a notification tap or button
+ * press. Only the OS itself may use this option.
+ * @hide
+ * @param broadcastIsInteractive
+ * @see #isInteractiveBroadcast()
+ */
+ public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
+ mIsInteractiveBroadcast = broadcastIsInteractive;
+ }
+
+ /**
+ * Did this broadcast originate with a direct user interaction?
+ * @return true if this broadcast is the result of an interaction, false otherwise
+ * @hide
+ */
+ public boolean isInteractiveBroadcast() {
+ return mIsInteractiveBroadcast;
+ }
+
+ /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -743,6 +773,9 @@ public class BroadcastOptions extends ComponentOptions {
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
+ if (mIsInteractiveBroadcast) {
+ b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
+ }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4ddfdb603e73..08a6b8c4e135 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -85,6 +85,7 @@ import android.credentials.CredentialManager;
import android.credentials.ICredentialManager;
import android.debug.AdbManager;
import android.debug.IAdbManager;
+import android.devicelock.DeviceLockFrameworkInitializer;
import android.graphics.fonts.FontManager;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -1555,6 +1556,7 @@ public final class SystemServiceRegistry {
ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
NearbyFrameworkInitializer.registerServiceWrappers();
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
+ DeviceLockFrameworkInitializer.registerServiceWrappers();
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java
index 76bad58e924b..752caac0c5cd 100644
--- a/core/java/android/app/time/TimeCapabilities.java
+++ b/core/java/android/app/time/TimeCapabilities.java
@@ -20,6 +20,7 @@ import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.time.Capabilities.CapabilityState;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,6 +38,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class TimeCapabilities implements Parcelable {
public static final @NonNull Creator<TimeCapabilities> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index b6a081825757..c9a45e04227a 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -17,6 +17,7 @@
package android.app.time;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class TimeCapabilitiesAndConfig implements Parcelable {
public static final @NonNull Creator<TimeCapabilitiesAndConfig> CREATOR =
diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java
index 7d986983160e..048f85a1e1a4 100644
--- a/core/java/android/app/time/TimeConfiguration.java
+++ b/core/java/android/app/time/TimeConfiguration.java
@@ -18,6 +18,7 @@ package android.app.time;
import android.annotation.NonNull;
import android.annotation.StringDef;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,6 +41,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class TimeConfiguration implements Parcelable {
public static final @NonNull Creator<TimeConfiguration> CREATOR =
@@ -155,6 +157,7 @@ public final class TimeConfiguration implements Parcelable {
*
* @hide
*/
+ @SystemApi
public static final class Builder {
private final Bundle mBundle = new Bundle();
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index 9f66f094786b..e35e359424e2 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -88,8 +88,6 @@ public final class TimeManager {
/**
* Returns the calling user's time capabilities and configuration.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
@NonNull
@@ -107,10 +105,26 @@ public final class TimeManager {
/**
* Modifies the time detection configuration.
*
- * @return {@code true} if all the configuration settings specified have been set to the
- * new values, {@code false} if none have
+ * <p>The ability to modify configuration settings can be subject to restrictions. For
+ * example, they may be determined by device hardware, general policy (i.e. only the primary
+ * user can set them), or by a managed device policy. Use {@link
+ * #getTimeCapabilitiesAndConfig()} to obtain information at runtime about the user's
+ * capabilities.
+ *
+ * <p>Attempts to modify configuration settings with capabilities that are {@link
+ * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link
+ * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
+ * will be returned. Modifying configuration settings with capabilities that are {@link
+ * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link
+ * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link
+ * TimeZoneCapabilities} for further details.
*
- * @hide
+ * <p>If the supplied configuration only has some values set, then only the specified settings
+ * will be updated (where the user's capabilities allow) and other settings will be left
+ * unchanged.
+ *
+ * @return {@code true} if all the configuration settings specified have been set to the
+ * new values, {@code false} if none have
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) {
@@ -280,8 +294,6 @@ public final class TimeManager {
/**
* Returns a snapshot of the device's current system clock time state. See also {@link
* #confirmTime(UnixEpochTime)} for how this information can be used.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
@NonNull
@@ -306,8 +318,6 @@ public final class TimeManager {
* <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being
* confirmed is no longer the time the device is currently set to. Confirming a time
* in which the system already has high confidence will return {@code true}.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) {
@@ -329,8 +339,6 @@ public final class TimeManager {
* capabilities prevents the time being accepted, e.g. if the device is currently set to
* "automatic time detection". This method returns {@code true} if the time was accepted even
* if it is the same as the current device time.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) {
@@ -353,8 +361,6 @@ public final class TimeManager {
* Returns a snapshot of the device's current time zone state. See also {@link
* #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may
* be used.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
@NonNull
@@ -379,8 +385,6 @@ public final class TimeManager {
* <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being
* confirmed is no longer the time zone ID the device is currently set to. Confirming a time
* zone ID in which the system already has high confidence returns {@code true}.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean confirmTimeZone(@NonNull String timeZoneId) {
@@ -402,8 +406,6 @@ public final class TimeManager {
* capabilities prevents the time zone being accepted, e.g. if the device is currently set to
* "automatic time zone detection". {@code true} is returned if the time zone is accepted. A
* time zone that is accepted and matches the current device time zone returns {@code true}.
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
public boolean setManualTimeZone(@NonNull String timeZoneId) {
diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java
index 01c869d99338..c209cde2cf49 100644
--- a/core/java/android/app/time/TimeState.java
+++ b/core/java/android/app/time/TimeState.java
@@ -18,6 +18,7 @@ package android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
@@ -36,6 +37,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class TimeState implements Parcelable {
public static final @NonNull Creator<TimeState> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 2f147cef9ffe..b647fc33055d 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -114,8 +114,6 @@ public final class TimeZoneCapabilities implements Parcelable {
* <p>The time zone will be ignored in all cases unless the value is {@link
* Capabilities#CAPABILITY_POSSESSED}. See also
* {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
- *
- * @hide
*/
@CapabilityState
public int getSetManualTimeZoneCapability() {
diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java
index 8e87111986ce..beb6dc6d1dfb 100644
--- a/core/java/android/app/time/TimeZoneState.java
+++ b/core/java/android/app/time/TimeZoneState.java
@@ -18,6 +18,7 @@ package android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
@@ -36,6 +37,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class TimeZoneState implements Parcelable {
public static final @NonNull Creator<TimeZoneState> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java
index 576bf6453eca..3a35f3cd1acb 100644
--- a/core/java/android/app/time/UnixEpochTime.java
+++ b/core/java/android/app/time/UnixEpochTime.java
@@ -19,6 +19,7 @@ package android.app.time;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
@@ -38,6 +39,7 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
public final class UnixEpochTime implements Parcelable {
@ElapsedRealtimeLong private final long mElapsedRealtimeMillis;
private final long mUnixEpochTimeMillis;
@@ -153,9 +155,8 @@ public final class UnixEpochTime implements Parcelable {
* Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
* Unix epoch time by the difference between the elapsed realtime value supplied and the one
* associated with this instance.
- *
- * @hide
*/
+ @NonNull
public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) {
long adjustedUnixEpochTimeMillis =
(elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 24e47bf9e47c..2dced96d3583 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -329,6 +329,22 @@ public class AppWidgetHost {
}
/**
+ * Set the visibiity of all widgets associated with this host to hidden
+ *
+ * @hide
+ */
+ public void setAppWidgetHidden() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setAppWidgetHidden(mContextOpPackageName, mHostId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("System server dead?", e);
+ }
+ }
+
+ /**
* Set the host's interaction handler.
*
* @hide
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 753c93612f40..d65210b8a0bc 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3938,6 +3938,7 @@ public abstract class Context {
//@hide: SAFETY_CENTER_SERVICE,
DISPLAY_HASH_SERVICE,
CREDENTIAL_SERVICE,
+ DEVICE_LOCK_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -6073,6 +6074,14 @@ public abstract class Context {
public static final String CREDENTIAL_SERVICE = "credential";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.devicelock.DeviceLockManager}.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String DEVICE_LOCK_SERVICE = "device_lock";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index db991dcd3afc..823c14281818 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4194,6 +4194,14 @@ public abstract class PackageManager {
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CREDENTIALS = "android.software.credentials";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports locking (for example, by a financing provider in case of a missed
+ * payment).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
new file mode 100644
index 000000000000..cf5f0363208a
--- /dev/null
+++ b/core/java/android/credentials/ui/BaseDialogResult.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 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 android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Base dialog result data.
+ *
+ * Returned for simple use cases like cancellation. Can also be subclassed when more information
+ * is needed, e.g. {@link UserSelectionDialogResult}.
+ *
+ * @hide
+ */
+public class BaseDialogResult implements Parcelable {
+ /** Parses and returns a BaseDialogResult from the given resultData. */
+ @Nullable
+ public static BaseDialogResult fromResultData(@NonNull Bundle resultData) {
+ return resultData.getParcelable(EXTRA_BASE_RESULT, BaseDialogResult.class);
+ }
+
+ /**
+ * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+ * ResultReceiver}.
+ */
+ public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
+ bundle.putParcelable(EXTRA_BASE_RESULT, result);
+ }
+
+ /**
+ * The intent extra key for the {@code BaseDialogResult} object when the credential
+ * selector activity finishes.
+ */
+ private static final String EXTRA_BASE_RESULT =
+ "android.credentials.ui.extra.BASE_RESULT";
+
+ /** User intentionally canceled the dialog. */
+ public static final int RESULT_CODE_DIALOG_CANCELED = 0;
+ /**
+ * User made a selection and the dialog finished. The user selection result is in the
+ * {@code resultData}.
+ */
+ public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 1;
+ /**
+ * The user has acknowledged the consent page rendered for when they first used Credential
+ * Manager on this device.
+ */
+ public static final int RESULT_CODE_CREDENTIAL_MANAGER_CONSENT_ACKNOWLEDGED = 2;
+ /**
+ * The user has acknowledged the consent page rendered for enabling a new provider.
+ * This should only happen during the first time use. The provider info is in the
+ * {@code resultData}.
+ */
+ public static final int RESULT_CODE_PROVIDER_ENABLED = 3;
+ /**
+ * The user has consented to switching to a new default provider. The provider info is in the
+ * {@code resultData}.
+ */
+ public static final int RESULT_CODE_DEFAULT_PROVIDER_CHANGED = 4;
+
+ @NonNull
+ private final IBinder mRequestToken;
+
+ public BaseDialogResult(@NonNull IBinder requestToken) {
+ mRequestToken = requestToken;
+ }
+
+ /** Returns the unique identifier for the request that launched the operation. */
+ @NonNull
+ public IBinder getRequestToken() {
+ return mRequestToken;
+ }
+
+ protected BaseDialogResult(@NonNull Parcel in) {
+ IBinder requestToken = in.readStrongBinder();
+ mRequestToken = requestToken;
+ AnnotationValidations.validate(NonNull.class, null, mRequestToken);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mRequestToken);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<BaseDialogResult> CREATOR =
+ new Creator<BaseDialogResult>() {
+ @Override
+ public BaseDialogResult createFromParcel(@NonNull Parcel in) {
+ return new BaseDialogResult(in);
+ }
+
+ @Override
+ public BaseDialogResult[] newArray(int size) {
+ return new BaseDialogResult[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
index aeeede744188..53ad40df2252 100644
--- a/core/java/android/credentials/ui/Constants.java
+++ b/core/java/android/credentials/ui/Constants.java
@@ -29,5 +29,4 @@ public class Constants {
*/
public static final String EXTRA_RESULT_RECEIVER =
"android.credentials.ui.extra.RESULT_RECEIVER";
-
}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 122c54ad8144..b9ee72dcdcf8 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -30,12 +30,39 @@ import com.android.internal.util.AnnotationValidations;
* @hide
*/
public class Entry implements Parcelable {
- // TODO: move to jetpack.
+ // TODO: these constants should go to jetpack.
public static final String VERSION = "v1";
public static final Uri CREDENTIAL_MANAGER_ENTRY_URI = Uri.parse("credentialmanager.slice");
- public static final String HINT_TITLE = "hint_title";
- public static final String HINT_SUBTITLE = "hint_subtitle";
- public static final String HINT_ICON = "hint_icon";
+ // TODO: remove these hint constants and use the credential entry & action ones defined below.
+ public static final String HINT_TITLE = "HINT_TITLE";
+ public static final String HINT_SUBTITLE = "HINT_SUBTITLE";
+ public static final String HINT_ICON = "HINT_ICON";
+ /**
+ * 1. CREDENTIAL ENTRY CONSTANTS
+ */
+ // User profile picture associated with this credential entry.
+ public static final String HINT_PROFILE_ICON = "HINT_PROFILE_ICON";
+ public static final String HINT_CREDENTIAL_TYPE_ICON = "HINT_CREDENTIAL_TYPE_ICON";
+ // The user account name of this provider app associated with this entry.
+ // Note: this is independent from the request app.
+ public static final String HINT_USER_PROVIDER_ACCOUNT_NAME = "HINT_USER_PROVIDER_ACCOUNT_NAME";
+ public static final String HINT_PASSWORD_COUNT = "HINT_PASSWORD_COUNT";
+ public static final String HINT_PASSKEY_COUNT = "HINT_PASSKEY_COUNT";
+ public static final String HINT_TOTAL_CREDENTIAL_COUNT = "HINT_TOTAL_CREDENTIAL_COUNT";
+ public static final String HINT_LAST_USED_TIME_MILLIS = "HINT_LAST_USED_TIME_MILLIS";
+ /** Below are only available for get flows. */
+ public static final String HINT_NOTE = "HINT_NOTE";
+ public static final String HINT_USER_NAME = "HINT_USER_NAME";
+ public static final String HINT_CREDENTIAL_TYPE = "HINT_CREDENTIAL_TYPE";
+ public static final String HINT_PASSKEY_USER_DISPLAY_NAME = "HINT_PASSKEY_USER_DISPLAY_NAME";
+ public static final String HINT_PASSWORD_VALUE = "HINT_PASSWORD_VALUE";
+
+ /**
+ * 2. ACTION CONSTANTS
+ */
+ public static final String HINT_ACTION_TITLE = "HINT_ACTION_TITLE";
+ public static final String HINT_ACTION_SUBTEXT = "HINT_ACTION_SUBTEXT";
+ public static final String HINT_ACTION_ICON = "HINT_ACTION_ICON";
/**
* The intent extra key for the action chip {@code Entry} list when launching the UX activities.
@@ -55,7 +82,7 @@ public class Entry implements Parcelable {
public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION =
"android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
- // TODO: may be changed to other type depending on the service implementation.
+ // TODO: change to string key + string subkey.
private final int mId;
@NonNull
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 9a038d137434..1b70ea4ebd71 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -34,8 +34,7 @@ public class IntentFactory {
ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) {
Intent intent = new Intent();
// TODO: define these as proper config strings.
- String activityName = "com.androidauth.tatiaccountselector/.CredentialSelectorActivity";
- // String activityName = "com.android.credentialmanager/.CredentialSelectorActivity";
+ String activityName = "com.android.credentialmanager/.CredentialSelectorActivity";
intent.setComponent(ComponentName.unflattenFromString(activityName));
intent.putParcelableArrayListExtra(
diff --git a/core/java/android/credentials/ui/ProviderDialogResult.java b/core/java/android/credentials/ui/ProviderDialogResult.java
new file mode 100644
index 000000000000..9d1be2063423
--- /dev/null
+++ b/core/java/android/credentials/ui/ProviderDialogResult.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 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 android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Result data matching {@link BaseDialogResult#RESULT_CODE_PROVIDER_ENABLED}, or {@link
+ * BaseDialogResult#RESULT_CODE_DEFAULT_PROVIDER_CHANGED}.
+ *
+ * @hide
+ */
+public class ProviderDialogResult extends BaseDialogResult implements Parcelable {
+ /** Parses and returns a ProviderDialogResult from the given resultData. */
+ @Nullable
+ public static ProviderDialogResult fromResultData(@NonNull Bundle resultData) {
+ return resultData.getParcelable(EXTRA_PROVIDER_RESULT, ProviderDialogResult.class);
+ }
+
+ /**
+ * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+ * ResultReceiver}.
+ */
+ public static void addToBundle(
+ @NonNull ProviderDialogResult result, @NonNull Bundle bundle) {
+ bundle.putParcelable(EXTRA_PROVIDER_RESULT, result);
+ }
+
+ /**
+ * The intent extra key for the {@code ProviderDialogResult} object when the credential
+ * selector activity finishes.
+ */
+ private static final String EXTRA_PROVIDER_RESULT =
+ "android.credentials.ui.extra.PROVIDER_RESULT";
+
+ @NonNull
+ private final String mProviderId;
+
+ public ProviderDialogResult(@NonNull IBinder requestToken, @NonNull String providerId) {
+ super(requestToken);
+ mProviderId = providerId;
+ }
+
+ @NonNull
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ protected ProviderDialogResult(@NonNull Parcel in) {
+ super(in);
+ String providerId = in.readString8();
+ mProviderId = providerId;
+ AnnotationValidations.validate(NonNull.class, null, mProviderId);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString8(mProviderId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<ProviderDialogResult> CREATOR =
+ new Creator<ProviderDialogResult>() {
+ @Override
+ public ProviderDialogResult createFromParcel(@NonNull Parcel in) {
+ return new ProviderDialogResult(in);
+ }
+
+ @Override
+ public ProviderDialogResult[] newArray(int size) {
+ return new ProviderDialogResult[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index eddb519051a9..619b08ec9ca7 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -17,12 +17,19 @@
package android.credentials.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialRequest;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Contains information about the request that initiated this UX flow.
*
@@ -42,18 +49,45 @@ public class RequestInfo implements Parcelable {
/** Type value for an executeCreateCredential request. */
public static final @NonNull String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(value = { TYPE_GET, TYPE_CREATE })
+ public @interface RequestType {}
+
@NonNull
private final IBinder mToken;
+ @Nullable
+ private final CreateCredentialRequest mCreateCredentialRequest;
+
+ @Nullable
+ private final GetCredentialRequest mGetCredentialRequest;
+
@NonNull
+ @RequestType
private final String mType;
private final boolean mIsFirstUsage;
- public RequestInfo(@NonNull IBinder token, @NonNull String type, boolean isFirstUsage) {
- mToken = token;
- mType = type;
- mIsFirstUsage = isFirstUsage;
+ @NonNull
+ private final String mAppDisplayName;
+
+ /** Creates new {@code RequestInfo} for a create-credential flow. */
+ public static RequestInfo newCreateRequestInfo(
+ @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
+ boolean isFirstUsage, @NonNull String appDisplayName) {
+ return new RequestInfo(
+ token, TYPE_CREATE, isFirstUsage, appDisplayName,
+ createCredentialRequest, null);
+ }
+
+ /** Creates new {@code RequestInfo} for a get-credential flow. */
+ public static RequestInfo newGetRequestInfo(
+ @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
+ boolean isFirstUsage, @NonNull String appDisplayName) {
+ return new RequestInfo(
+ token, TYPE_GET, isFirstUsage, appDisplayName,
+ null, getCredentialRequest);
}
/** Returns the request token matching the user request. */
@@ -64,6 +98,7 @@ public class RequestInfo implements Parcelable {
/** Returns the request type. */
@NonNull
+ @RequestType
public String getType() {
return mType;
}
@@ -78,16 +113,61 @@ public class RequestInfo implements Parcelable {
return mIsFirstUsage;
}
+ /** Returns the display name of the app that made this request. */
+ @NonNull
+ public String getAppDisplayName() {
+ return mAppDisplayName;
+ }
+
+ /**
+ * Returns the non-null CreateCredentialRequest when the type of the request is {@link
+ * #TYPE_CREATE}, or null otherwise.
+ */
+ @Nullable
+ public CreateCredentialRequest getCreateCredentialRequest() {
+ return mCreateCredentialRequest;
+ }
+
+ /**
+ * Returns the non-null GetCredentialRequest when the type of the request is {@link
+ * #TYPE_GET}, or null otherwise.
+ */
+ @Nullable
+ public GetCredentialRequest getGetCredentialRequest() {
+ return mGetCredentialRequest;
+ }
+
+ private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
+ boolean isFirstUsage, @NonNull String appDisplayName,
+ @Nullable CreateCredentialRequest createCredentialRequest,
+ @Nullable GetCredentialRequest getCredentialRequest) {
+ mToken = token;
+ mType = type;
+ mIsFirstUsage = isFirstUsage;
+ mAppDisplayName = appDisplayName;
+ mCreateCredentialRequest = createCredentialRequest;
+ mGetCredentialRequest = getCredentialRequest;
+ }
+
protected RequestInfo(@NonNull Parcel in) {
IBinder token = in.readStrongBinder();
String type = in.readString8();
boolean isFirstUsage = in.readBoolean();
+ String appDisplayName = in.readString8();
+ CreateCredentialRequest createCredentialRequest =
+ in.readTypedObject(CreateCredentialRequest.CREATOR);
+ GetCredentialRequest getCredentialRequest =
+ in.readTypedObject(GetCredentialRequest.CREATOR);
mToken = token;
AnnotationValidations.validate(NonNull.class, null, mToken);
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
mIsFirstUsage = isFirstUsage;
+ mAppDisplayName = appDisplayName;
+ AnnotationValidations.validate(NonNull.class, null, mAppDisplayName);
+ mCreateCredentialRequest = createCredentialRequest;
+ mGetCredentialRequest = getCredentialRequest;
}
@Override
@@ -95,6 +175,9 @@ public class RequestInfo implements Parcelable {
dest.writeStrongBinder(mToken);
dest.writeString8(mType);
dest.writeBoolean(mIsFirstUsage);
+ dest.writeString8(mAppDisplayName);
+ dest.writeTypedObject(mCreateCredentialRequest, flags);
+ dest.writeTypedObject(mGetCredentialRequest, flags);
}
@Override
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
index 2ac559381c6e..eb3a4a8cfcbd 100644
--- a/core/java/android/credentials/ui/UserSelectionResult.java
+++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java
@@ -17,6 +17,8 @@
package android.credentials.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,24 +26,33 @@ import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
/**
- * User selection result information of a UX flow.
- *
- * Returned as part of the activity result intent data when the user dialog completes
- * successfully.
+ * Result data matching {@link BaseDialogResult#RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION}.
*
* @hide
*/
-public class UserSelectionResult implements Parcelable {
+public class UserSelectionDialogResult extends BaseDialogResult implements Parcelable {
+ /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+ @Nullable
+ public static UserSelectionDialogResult fromResultData(@NonNull Bundle resultData) {
+ return resultData.getParcelable(
+ EXTRA_USER_SELECTION_RESULT, UserSelectionDialogResult.class);
+ }
/**
- * The intent extra key for the {@code UserSelectionResult} object when the credential selector
- * activity finishes.
- */
- public static final String EXTRA_USER_SELECTION_RESULT =
- "android.credentials.ui.extra.USER_SELECTION_RESULT";
+ * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+ * ResultReceiver}.
+ */
+ public static void addToBundle(
+ @NonNull UserSelectionDialogResult result, @NonNull Bundle bundle) {
+ bundle.putParcelable(EXTRA_USER_SELECTION_RESULT, result);
+ }
- @NonNull
- private final IBinder mRequestToken;
+ /**
+ * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+ * selector activity finishes.
+ */
+ private static final String EXTRA_USER_SELECTION_RESULT =
+ "android.credentials.ui.extra.USER_SELECTION_RESULT";
@NonNull
private final String mProviderId;
@@ -49,19 +60,14 @@ public class UserSelectionResult implements Parcelable {
// TODO: consider switching to string or other types, depending on the service implementation.
private final int mEntryId;
- public UserSelectionResult(@NonNull IBinder requestToken, @NonNull String providerId,
+ public UserSelectionDialogResult(
+ @NonNull IBinder requestToken, @NonNull String providerId,
int entryId) {
- mRequestToken = requestToken;
+ super(requestToken);
mProviderId = providerId;
mEntryId = entryId;
}
- /** Returns token of the app request that initiated this user dialog. */
- @NonNull
- public IBinder getRequestToken() {
- return mRequestToken;
- }
-
/** Returns provider package name whose entry was selected by the user. */
@NonNull
public String getProviderId() {
@@ -73,13 +79,11 @@ public class UserSelectionResult implements Parcelable {
return mEntryId;
}
- protected UserSelectionResult(@NonNull Parcel in) {
- IBinder requestToken = in.readStrongBinder();
+ protected UserSelectionDialogResult(@NonNull Parcel in) {
+ super(in);
String providerId = in.readString8();
int entryId = in.readInt();
- mRequestToken = requestToken;
- AnnotationValidations.validate(NonNull.class, null, mRequestToken);
mProviderId = providerId;
AnnotationValidations.validate(NonNull.class, null, mProviderId);
mEntryId = entryId;
@@ -87,7 +91,7 @@ public class UserSelectionResult implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mRequestToken);
+ super.writeToParcel(dest, flags);
dest.writeString8(mProviderId);
dest.writeInt(mEntryId);
}
@@ -97,16 +101,16 @@ public class UserSelectionResult implements Parcelable {
return 0;
}
- public static final @NonNull Creator<UserSelectionResult> CREATOR =
- new Creator<UserSelectionResult>() {
+ public static final @NonNull Creator<UserSelectionDialogResult> CREATOR =
+ new Creator<UserSelectionDialogResult>() {
@Override
- public UserSelectionResult createFromParcel(@NonNull Parcel in) {
- return new UserSelectionResult(in);
+ public UserSelectionDialogResult createFromParcel(@NonNull Parcel in) {
+ return new UserSelectionDialogResult(in);
}
@Override
- public UserSelectionResult[] newArray(int size) {
- return new UserSelectionResult[size];
+ public UserSelectionDialogResult[] newArray(int size) {
+ return new UserSelectionDialogResult[size];
}
};
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index dff2f7ed1cf3..50551feed522 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -133,9 +133,6 @@ public final class CameraManager {
private HandlerThread mHandlerThread;
private Handler mHandler;
private FoldStateListener mFoldStateListener;
- @GuardedBy("mLock")
- private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = new ArrayList<>();
- private boolean mFoldedDeviceState;
/**
* @hide
@@ -144,31 +141,39 @@ public final class CameraManager {
void onDeviceStateChanged(boolean folded);
}
- private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+ private static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
private final int[] mFoldedDeviceStates;
+ private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners =
+ new ArrayList<>();
+ private boolean mFoldedDeviceState;
+
public FoldStateListener(Context context) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
}
- private void handleStateChange(int state) {
+ private synchronized void handleStateChange(int state) {
boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
- synchronized (mLock) {
- mFoldedDeviceState = folded;
- ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
- for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
- DeviceStateListener callback = listener.get();
- if (callback != null) {
- callback.onDeviceStateChanged(folded);
- } else {
- invalidListeners.add(listener);
- }
- }
- if (!invalidListeners.isEmpty()) {
- mDeviceStateListeners.removeAll(invalidListeners);
+
+ mFoldedDeviceState = folded;
+ ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
+ for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
+ DeviceStateListener callback = listener.get();
+ if (callback != null) {
+ callback.onDeviceStateChanged(folded);
+ } else {
+ invalidListeners.add(listener);
}
}
+ if (!invalidListeners.isEmpty()) {
+ mDeviceStateListeners.removeAll(invalidListeners);
+ }
+ }
+
+ public synchronized void addDeviceStateListener(DeviceStateListener listener) {
+ listener.onDeviceStateChanged(mFoldedDeviceState);
+ mDeviceStateListeners.add(new WeakReference<>(listener));
}
@Override
@@ -192,9 +197,8 @@ public final class CameraManager {
public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) {
synchronized (mLock) {
DeviceStateListener listener = chars.getDeviceStateListener();
- listener.onDeviceStateChanged(mFoldedDeviceState);
if (mFoldStateListener != null) {
- mDeviceStateListeners.add(new WeakReference<>(listener));
+ mFoldStateListener.addDeviceStateListener(listener);
}
}
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7247ef77afb4..197739b6a067 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -768,6 +768,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
+ /**
+ * Schedules a watchdog.
+ *
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void scheduleWatchdog() {
+ try {
+ mService.scheduleWatchdog();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void cancelEnrollment(long requestId) {
if (mService != null) {
try {
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9b56f43a0f22..2bf187ac9006 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -172,4 +172,9 @@ interface IFaceService {
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
+
+ // Internal operation used to clear face biometric scheduler.
+ // Ensures that the scheduler is not stuck.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void scheduleWatchdog();
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 0fd164de8ffb..5403f089b308 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1080,7 +1080,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
public boolean isPowerbuttonFps() {
final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
- return sensorProps.sensorType == TYPE_POWER_BUTTON;
+ return sensorProps == null ? false : sensorProps.sensorType == TYPE_POWER_BUTTON;
}
/**
@@ -1125,6 +1125,20 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
+ * Schedules a watchdog.
+ *
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void scheduleWatchdog() {
+ try {
+ mService.scheduleWatchdog();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
*/
public void addLockoutResetCallback(final LockoutResetCallback callback) {
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 1ba9a0471c88..051e3a4caa4e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -208,4 +208,9 @@ interface IFingerprintService {
// Sends a power button pressed event to all listeners.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
oneway void onPowerPressed();
+
+ // Internal operation used to clear fingerprint biometric scheduler.
+ // Ensures that the scheduler is not stuck.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void scheduleWatchdog();
}
diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java
index 8febed3fb2a0..3ba3ebceeb18 100644
--- a/core/java/android/hardware/radio/Announcement.java
+++ b/core/java/android/hardware/radio/Announcement.java
@@ -85,9 +85,9 @@ public final class Announcement implements Parcelable {
/** @hide */
public Announcement(@NonNull ProgramSelector selector, @Type int type,
@NonNull Map<String, String> vendorInfo) {
- mSelector = Objects.requireNonNull(selector);
- mType = Objects.requireNonNull(type);
- mVendorInfo = Objects.requireNonNull(vendorInfo);
+ mSelector = Objects.requireNonNull(selector, "Program selector cannot be null");
+ mType = type;
+ mVendorInfo = Objects.requireNonNull(vendorInfo, "Vendor info cannot be null");
}
private Announcement(@NonNull Parcel in) {
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index f2525d17e30a..ade9fd68ade8 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -160,6 +160,7 @@ public final class ProgramList implements AutoCloseable {
* Disables list updates and releases all resources.
*/
public void close() {
+ OnCloseListener onCompleteListenersCopied = null;
synchronized (mLock) {
if (mIsClosed) return;
mIsClosed = true;
@@ -167,10 +168,14 @@ public final class ProgramList implements AutoCloseable {
mListCallbacks.clear();
mOnCompleteListeners.clear();
if (mOnCloseListener != null) {
- mOnCloseListener.onClose();
+ onCompleteListenersCopied = mOnCloseListener;
mOnCloseListener = null;
}
}
+
+ if (onCompleteListenersCopied != null) {
+ onCompleteListenersCopied.onClose();
+ }
}
void apply(Chunk chunk) {
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index 1f686e5c449c..c80c57ce917a 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -1,11 +1,15 @@
# Bug component: 95221
+# Android Storage Team
+abkaur@google.com
corinac@google.com
-nandana@google.com
-zezeozue@google.com
-maco@google.com
+dipankarb@google.com
+krishang@google.com
sahanas@google.com
-abkaur@google.com
-chiangi@google.com
+sergeynv@google.com
+shubhisaxena@google.com
+tylersaunders@google.com
+
+maco@google.com
+nandana@google.com
narayan@google.com
-dipankarb@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4e15b38463d6..29e24598f874 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6875,6 +6875,14 @@ public final class Settings {
@Readable
public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+
+ /**
+ * The currently selected credential service(s) flattened ComponentName.
+ *
+ * @hide
+ */
+ public static final String CREDENTIAL_SERVICE = "credential_service";
+
/**
* The currently selected autofill service flattened ComponentName.
* @hide
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index e2c11fbac008..553a32419533 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -50,9 +50,8 @@ public final class Action implements Parcelable {
}
private Action(@NonNull Parcel in) {
- mSlice = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
- mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
- PendingIntent.class);
+ mSlice = in.readTypedObject(Slice.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
}
public static final @NonNull Creator<Action> CREATOR = new Creator<Action>() {
@@ -74,8 +73,8 @@ public final class Action implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mSlice.writeToParcel(dest, flags);
- mPendingIntent.writeToParcel(dest, flags);
+ dest.writeTypedObject(mSlice, flags);
+ dest.writeTypedObject(mPendingIntent, flags);
}
/**
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
index 6a0bbc0bd917..e6da349a2fbe 100644
--- a/core/java/android/service/credentials/CreateCredentialRequest.java
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -54,7 +54,7 @@ public final class CreateCredentialRequest implements Parcelable {
private CreateCredentialRequest(@NonNull Parcel in) {
mCallingPackage = in.readString8();
mType = in.readString8();
- mData = in.readBundle();
+ mData = in.readTypedObject(Bundle.CREATOR);
}
public static final @NonNull Creator<CreateCredentialRequest> CREATOR =
@@ -79,7 +79,7 @@ public final class CreateCredentialRequest implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mCallingPackage);
dest.writeString8(mType);
- dest.writeBundle(mData);
+ dest.writeTypedObject(mData, flags);
}
/** Returns the calling package of the calling app. */
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index 613eba8c9bb2..559b1caab87c 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -38,7 +38,9 @@ public final class CreateCredentialResponse implements Parcelable {
private CreateCredentialResponse(@NonNull Parcel in) {
mHeader = in.readCharSequence();
- mSaveEntries = in.createTypedArrayList(SaveEntry.CREATOR);
+ List<SaveEntry> saveEntries = new ArrayList<>();
+ in.readTypedList(saveEntries, SaveEntry.CREATOR);
+ mSaveEntries = saveEntries;
}
@Override
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 49b84359d94a..4cc43a10e88f 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -65,12 +65,10 @@ public final class CredentialEntry implements Parcelable {
}
private CredentialEntry(@NonNull Parcel in) {
- mType = in.readString();
- mSlice = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
- mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
- PendingIntent.class);
- mCredential = in.readParcelable(Credential.class.getClassLoader(),
- Credential.class);
+ mType = in.readString8();
+ mSlice = in.readTypedObject(Slice.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mCredential = in.readTypedObject(Credential.CREATOR);
mAutoSelectAllowed = in.readBoolean();
}
@@ -95,9 +93,9 @@ public final class CredentialEntry implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
- mSlice.writeToParcel(dest, flags);
- mPendingIntent.writeToParcel(dest, flags);
- mCredential.writeToParcel(dest, flags);
+ dest.writeTypedObject(mSlice, flags);
+ dest.writeTypedObject(mPendingIntent, flags);
+ dest.writeTypedObject(mCredential, flags);
dest.writeBoolean(mAutoSelectAllowed);
}
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
new file mode 100644
index 000000000000..e3f8cb7bb23e
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -0,0 +1,184 @@
+/*
+ * 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 android.service.credentials;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a credential provider.
+ *
+ * @hide
+ */
+public final class CredentialProviderInfo {
+ private static final String TAG = "CredentialProviderInfo";
+
+ @NonNull
+ private final ServiceInfo mServiceInfo;
+ @NonNull
+ private final List<String> mCapabilities;
+
+ // TODO: Move the two strings below to CredentialProviderService when ready.
+ private static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+ private static final String SERVICE_INTERFACE =
+ "android.service.credentials.CredentialProviderService";
+
+
+ /**
+ * Constructs an information instance of the credential provider.
+ *
+ * @param context The context object
+ * @param serviceComponent The serviceComponent of the provider service
+ * @param userId The android userId for which the current process is running
+ * @throws PackageManager.NameNotFoundException If provider service is not found
+ */
+ public CredentialProviderInfo(@NonNull Context context,
+ @NonNull ComponentName serviceComponent, int userId)
+ throws PackageManager.NameNotFoundException {
+ this(context, getServiceInfoOrThrow(serviceComponent, userId));
+ }
+
+ private CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
+ if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
+ Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
+ + "does not require permission"
+ + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
+ throw new SecurityException("Service does not require the expected permission : "
+ + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
+ }
+ mServiceInfo = serviceInfo;
+ mCapabilities = new ArrayList<>();
+ populateProviderCapabilities(context);
+ }
+
+ private void populateProviderCapabilities(@NonNull Context context) {
+ if (mServiceInfo.applicationInfo.metaData == null) {
+ return;
+ }
+ try {
+ final int resourceId = mServiceInfo.applicationInfo.metaData.getInt(
+ CAPABILITY_META_DATA_KEY);
+ String[] capabilities = context.getResources().getStringArray(resourceId);
+ if (capabilities == null) {
+ Log.w(TAG, "No capabilities found for provider: " + mServiceInfo.packageName);
+ return;
+ }
+ for (String capability : capabilities) {
+ if (capability.isEmpty()) {
+ Log.w(TAG, "Skipping empty capability");
+ continue;
+ }
+ mCapabilities.add(capability);
+ }
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Exception while populating provider capabilities: " + e.getMessage());
+ }
+ }
+
+ private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent,
+ int userId) throws PackageManager.NameNotFoundException {
+ try {
+ ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.GET_META_DATA,
+ userId);
+ if (si != null) {
+ return si;
+ }
+ } catch (RemoteException e) {
+ Slog.v(TAG, e.getMessage());
+ }
+ throw new PackageManager.NameNotFoundException(serviceComponent.toString());
+ }
+
+ /**
+ * Returns true if the service supports the given {@code credentialType}, false otherwise.
+ */
+ @NonNull
+ public boolean hasCapability(@NonNull String credentialType) {
+ return mCapabilities.contains(credentialType);
+ }
+
+ /** Returns the service info. */
+ @NonNull
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ /** Returns an immutable list of capabilities this provider service can support. */
+ @NonNull
+ public List<String> getCapabilities() {
+ return Collections.unmodifiableList(mCapabilities);
+ }
+
+ /**
+ * Returns the valid credential provider services available for the user with the
+ * given {@code userId}.
+ */
+ public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
+ @UserIdInt int userId) {
+ final List<CredentialProviderInfo> services = new ArrayList<>();
+
+ final List<ResolveInfo> resolveInfos =
+ context.getPackageManager().queryIntentServicesAsUser(
+ new Intent(SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA,
+ userId);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ try {
+ services.add(new CredentialProviderInfo(context, serviceInfo));
+ } catch (SecurityException e) {
+ Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
+ }
+ }
+ return services;
+ }
+
+ /**
+ * Returns the valid credential provider services available for the user, that can
+ * support the given {@code credentialType}.
+ */
+ public static List<CredentialProviderInfo> getAvailableServicesForCapability(
+ Context context, @UserIdInt int userId, String credentialType) {
+ List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
+ final List<CredentialProviderInfo> services = getAvailableServices(context, userId);
+
+ for (CredentialProviderInfo service : services) {
+ if (service.hasCapability(credentialType)) {
+ servicesForCapability.add(service);
+ }
+ }
+ return servicesForCapability;
+ }
+}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index 4133ea5955c4..2cce169e7a58 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -53,8 +53,12 @@ public final class CredentialsDisplayContent implements Parcelable {
private CredentialsDisplayContent(@NonNull Parcel in) {
mHeader = in.readCharSequence();
- mCredentialEntries = in.createTypedArrayList(CredentialEntry.CREATOR);
- mActions = in.createTypedArrayList(Action.CREATOR);
+ List<CredentialEntry> credentialEntries = new ArrayList<>();
+ in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
+ mCredentialEntries = credentialEntries;
+ List<Action> actions = new ArrayList<>();
+ in.readTypedList(actions, Action.CREATOR);
+ mActions = actions;
}
public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
@@ -78,8 +82,8 @@ public final class CredentialsDisplayContent implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeCharSequence(mHeader);
- dest.writeTypedList(mCredentialEntries);
- dest.writeTypedList(mActions);
+ dest.writeTypedList(mCredentialEntries, flags);
+ dest.writeTypedList(mActions, flags);
}
/**
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index 5b1a1713ee51..e06be4433062 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -49,8 +49,10 @@ public final class GetCredentialsRequest implements Parcelable {
}
private GetCredentialsRequest(@NonNull Parcel in) {
- mCallingPackage = in.readString16NoHelper();
- mGetCredentialOptions = in.createTypedArrayList(GetCredentialOption.CREATOR);
+ mCallingPackage = in.readString8();
+ List<GetCredentialOption> getCredentialOptions = new ArrayList<>();
+ in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
+ mGetCredentialOptions = getCredentialOptions;
}
public static final @NonNull Creator<GetCredentialsRequest> CREATOR =
@@ -73,7 +75,7 @@ public final class GetCredentialsRequest implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString16NoHelper(mCallingPackage);
+ dest.writeString8(mCallingPackage);
dest.writeTypedList(mGetCredentialOptions);
}
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
index 980d9ae47aa7..979a6993c3d4 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -78,9 +78,8 @@ public final class GetCredentialsResponse implements Parcelable {
}
private GetCredentialsResponse(@NonNull Parcel in) {
- mCredentialsDisplayContent = in.readParcelable(CredentialsDisplayContent.class
- .getClassLoader(), CredentialsDisplayContent.class);
- mAuthenticationAction = in.readParcelable(Action.class.getClassLoader(), Action.class);
+ mCredentialsDisplayContent = in.readTypedObject(CredentialsDisplayContent.CREATOR);
+ mAuthenticationAction = in.readTypedObject(Action.CREATOR);
}
public static final @NonNull Creator<GetCredentialsResponse> CREATOR =
@@ -103,8 +102,8 @@ public final class GetCredentialsResponse implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mCredentialsDisplayContent, flags);
- dest.writeParcelable(mAuthenticationAction, flags);
+ dest.writeTypedObject(mCredentialsDisplayContent, flags);
+ dest.writeTypedObject(mAuthenticationAction, flags);
}
/**
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
index 18644f09ecef..abe51d43bc48 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -40,10 +40,9 @@ public final class SaveEntry implements Parcelable {
private final @Nullable Credential mCredential;
private SaveEntry(@NonNull Parcel in) {
- mSlice = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
- mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
- PendingIntent.class);
- mCredential = in.readParcelable(Credential.class.getClassLoader(), Credential.class);
+ mSlice = in.readTypedObject(Slice.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mCredential = in.readTypedObject(Credential.CREATOR);
}
public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
@@ -65,9 +64,9 @@ public final class SaveEntry implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mSlice.writeToParcel(dest, flags);
- mPendingIntent.writeToParcel(dest, flags);
- mCredential.writeToParcel(dest, flags);
+ dest.writeTypedObject(mSlice, flags);
+ dest.writeTypedObject(mPendingIntent, flags);
+ dest.writeTypedObject(mCredential, flags);
}
/* package-private */ SaveEntry(
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index cb0dce91589e..32bdf7962273 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1047,7 +1047,7 @@ public class DreamService extends Service implements Window.Callback {
}
if (mDreamToken == null) {
- Slog.w(mTag, "Finish was called before the dream was attached.");
+ if (mDebug) Slog.v(mTag, "finish() called when not attached.");
stopSelf();
return;
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index bd4a4957775e..cfc79e4fef66 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -392,13 +392,13 @@ public abstract class NotificationListenerService extends Service {
public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3;
/**
- * An optional activity intent category that shows additional settings for what notifications
+ * An optional activity intent action that shows additional settings for what notifications
* should be processed by this notification listener service. If defined, the OS may link to
* this activity from the system notification listener service filter settings page.
*/
- @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
- public static final String INTENT_CATEGORY_SETTINGS_HOME =
- "android.service.notification.category.SETTINGS_HOME";
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SETTINGS_HOME =
+ "android.service.notification.action.SETTINGS_HOME";
private final Object mLock = new Object();
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index f6433b7f371e..714afee197f1 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -57,7 +57,7 @@ public final class TimeZoneProviderEvent implements Parcelable {
/**
* The provider was uncertain about the time zone. See {@link
- * TimeZoneProviderService#reportUncertain()}
+ * TimeZoneProviderService#reportUncertain(TimeZoneProviderStatus)}
*/
public static final @EventType int EVENT_TYPE_UNCERTAIN = 3;
@@ -66,42 +66,55 @@ public final class TimeZoneProviderEvent implements Parcelable {
@ElapsedRealtimeLong
private final long mCreationElapsedMillis;
+ // Populated when mType == EVENT_TYPE_SUGGESTION
@Nullable
private final TimeZoneProviderSuggestion mSuggestion;
+ // Populated when mType == EVENT_TYPE_PERMANENT_FAILURE
@Nullable
private final String mFailureCause;
- private TimeZoneProviderEvent(@EventType int type,
+ // Populated when mType == EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
+ @Nullable
+ private final TimeZoneProviderStatus mTimeZoneProviderStatus;
+
+ private TimeZoneProviderEvent(int type,
@ElapsedRealtimeLong long creationElapsedMillis,
@Nullable TimeZoneProviderSuggestion suggestion,
- @Nullable String failureCause) {
+ @Nullable String failureCause,
+ @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
mType = type;
mCreationElapsedMillis = creationElapsedMillis;
mSuggestion = suggestion;
mFailureCause = failureCause;
+ mTimeZoneProviderStatus = timeZoneProviderStatus;
}
- /** Returns a event of type {@link #EVENT_TYPE_SUGGESTION}. */
+ /** Returns an event of type {@link #EVENT_TYPE_SUGGESTION}. */
public static TimeZoneProviderEvent createSuggestionEvent(
@ElapsedRealtimeLong long creationElapsedMillis,
- @NonNull TimeZoneProviderSuggestion suggestion) {
+ @NonNull TimeZoneProviderSuggestion suggestion,
+ @NonNull TimeZoneProviderStatus providerStatus) {
return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
- Objects.requireNonNull(suggestion), null);
+ Objects.requireNonNull(suggestion), null, Objects.requireNonNull(providerStatus));
}
- /** Returns a event of type {@link #EVENT_TYPE_UNCERTAIN}. */
+ /** Returns an event of type {@link #EVENT_TYPE_UNCERTAIN}. */
public static TimeZoneProviderEvent createUncertainEvent(
- @ElapsedRealtimeLong long creationElapsedMillis) {
- return new TimeZoneProviderEvent(EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null);
+ @ElapsedRealtimeLong long creationElapsedMillis,
+ @NonNull TimeZoneProviderStatus timeZoneProviderStatus) {
+
+ return new TimeZoneProviderEvent(
+ EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null,
+ Objects.requireNonNull(timeZoneProviderStatus));
}
- /** Returns a event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
+ /** Returns an event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
public static TimeZoneProviderEvent createPermanentFailureEvent(
@ElapsedRealtimeLong long creationElapsedMillis,
@NonNull String cause) {
return new TimeZoneProviderEvent(EVENT_TYPE_PERMANENT_FAILURE, creationElapsedMillis, null,
- Objects.requireNonNull(cause));
+ Objects.requireNonNull(cause), null);
}
/**
@@ -126,7 +139,7 @@ public final class TimeZoneProviderEvent implements Parcelable {
}
/**
- * Returns the failure cauese. Populated when {@link #getType()} is {@link
+ * Returns the failure cause. Populated when {@link #getType()} is {@link
* #EVENT_TYPE_PERMANENT_FAILURE}.
*/
@Nullable
@@ -134,24 +147,34 @@ public final class TimeZoneProviderEvent implements Parcelable {
return mFailureCause;
}
- public static final @NonNull Creator<TimeZoneProviderEvent> CREATOR =
- new Creator<TimeZoneProviderEvent>() {
- @Override
- public TimeZoneProviderEvent createFromParcel(Parcel in) {
- int type = in.readInt();
- long creationElapsedMillis = in.readLong();
- TimeZoneProviderSuggestion suggestion =
- in.readParcelable(getClass().getClassLoader(), android.service.timezone.TimeZoneProviderSuggestion.class);
- String failureCause = in.readString8();
- return new TimeZoneProviderEvent(
- type, creationElapsedMillis, suggestion, failureCause);
- }
-
- @Override
- public TimeZoneProviderEvent[] newArray(int size) {
- return new TimeZoneProviderEvent[size];
- }
- };
+ /**
+ * Returns the status of the time zone provider. Populated when {@link #getType()} is {@link
+ * #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getTimeZoneProviderStatus() {
+ return mTimeZoneProviderStatus;
+ }
+
+ public static final @NonNull Creator<TimeZoneProviderEvent> CREATOR = new Creator<>() {
+ @Override
+ public TimeZoneProviderEvent createFromParcel(Parcel in) {
+ int type = in.readInt();
+ long creationElapsedMillis = in.readLong();
+ TimeZoneProviderSuggestion suggestion = in.readParcelable(
+ getClass().getClassLoader(), TimeZoneProviderSuggestion.class);
+ String failureCause = in.readString8();
+ TimeZoneProviderStatus status = in.readParcelable(
+ getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ return new TimeZoneProviderEvent(
+ type, creationElapsedMillis, suggestion, failureCause, status);
+ }
+
+ @Override
+ public TimeZoneProviderEvent[] newArray(int size) {
+ return new TimeZoneProviderEvent[size];
+ }
+ };
@Override
public int describeContents() {
@@ -164,6 +187,7 @@ public final class TimeZoneProviderEvent implements Parcelable {
parcel.writeLong(mCreationElapsedMillis);
parcel.writeParcelable(mSuggestion, 0);
parcel.writeString8(mFailureCause);
+ parcel.writeParcelable(mTimeZoneProviderStatus, 0);
}
@Override
@@ -173,14 +197,17 @@ public final class TimeZoneProviderEvent implements Parcelable {
+ ", mCreationElapsedMillis=" + Duration.ofMillis(mCreationElapsedMillis).toString()
+ ", mSuggestion=" + mSuggestion
+ ", mFailureCause=" + mFailureCause
+ + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus
+ '}';
}
/**
* Similar to {@link #equals} except this methods checks for equivalence, not equality.
- * i.e. two {@link #EVENT_TYPE_UNCERTAIN} and {@link #EVENT_TYPE_PERMANENT_FAILURE} events are
- * always equivalent, two {@link #EVENT_TYPE_SUGGESTION} events are equivalent if they suggest
- * the same time zones.
+ * i.e. two {@link #EVENT_TYPE_SUGGESTION} events are equivalent if they suggest
+ * the same time zones and have the same provider status, two {@link #EVENT_TYPE_UNCERTAIN}
+ * events are equivalent if they have the same provider status, and {@link
+ * #EVENT_TYPE_PERMANENT_FAILURE} events are always equivalent (the nature of the failure is not
+ * considered).
*/
@SuppressWarnings("ReferenceEquality")
public boolean isEquivalentTo(@Nullable TimeZoneProviderEvent other) {
@@ -191,9 +218,10 @@ public final class TimeZoneProviderEvent implements Parcelable {
return false;
}
if (mType == EVENT_TYPE_SUGGESTION) {
- return mSuggestion.isEquivalentTo(other.getSuggestion());
+ return mSuggestion.isEquivalentTo(other.mSuggestion)
+ && Objects.equals(mTimeZoneProviderStatus, other.mTimeZoneProviderStatus);
}
- return true;
+ return Objects.equals(mTimeZoneProviderStatus, other.mTimeZoneProviderStatus);
}
@Override
@@ -208,11 +236,13 @@ public final class TimeZoneProviderEvent implements Parcelable {
return mType == that.mType
&& mCreationElapsedMillis == that.mCreationElapsedMillis
&& Objects.equals(mSuggestion, that.mSuggestion)
- && Objects.equals(mFailureCause, that.mFailureCause);
+ && Objects.equals(mFailureCause, that.mFailureCause)
+ && Objects.equals(mTimeZoneProviderStatus, that.mTimeZoneProviderStatus);
}
@Override
public int hashCode() {
- return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause);
+ return Objects.hash(mType, mCreationElapsedMillis, mSuggestion, mFailureCause,
+ mTimeZoneProviderStatus);
}
}
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index 0d215f6d56f1..cd4a30598a9b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -203,6 +203,20 @@ public abstract class TimeZoneProviderService extends Service {
* details.
*/
public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) {
+ reportSuggestion(suggestion, TimeZoneProviderStatus.UNKNOWN);
+ }
+
+ /**
+ * Indicates a successful time zone detection. See {@link TimeZoneProviderSuggestion} for
+ * details.
+ *
+ * @param providerStatus provider status information that can influence detector service
+ * behavior and/or be reported via the device UI
+ *
+ * @hide
+ */
+ public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
+ @NonNull TimeZoneProviderStatus providerStatus) {
Objects.requireNonNull(suggestion);
mHandler.post(() -> {
@@ -212,7 +226,7 @@ public abstract class TimeZoneProviderService extends Service {
try {
TimeZoneProviderEvent thisEvent =
TimeZoneProviderEvent.createSuggestionEvent(
- SystemClock.elapsedRealtime(), suggestion);
+ SystemClock.elapsedRealtime(), suggestion, providerStatus);
if (shouldSendEvent(thisEvent)) {
manager.onTimeZoneProviderEvent(thisEvent);
mLastEventSent = thisEvent;
@@ -231,6 +245,21 @@ public abstract class TimeZoneProviderService extends Service {
* to a time zone.
*/
public final void reportUncertain() {
+ reportUncertain(TimeZoneProviderStatus.UNKNOWN);
+ }
+
+ /**
+ * Indicates the time zone is not known because of an expected runtime state or error.
+ *
+ * <p>When the status changes then a certain or uncertain report must be made to move the
+ * detector service to the new status.
+ *
+ * @param providerStatus provider status information that can influence detector service
+ * behavior and/or be reported via the device UI
+ *
+ * @hide
+ */
+ public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
mHandler.post(() -> {
synchronized (mLock) {
ITimeZoneProviderManager manager = mManager;
@@ -238,7 +267,7 @@ public abstract class TimeZoneProviderService extends Service {
try {
TimeZoneProviderEvent thisEvent =
TimeZoneProviderEvent.createUncertainEvent(
- SystemClock.elapsedRealtime());
+ SystemClock.elapsedRealtime(), providerStatus);
if (shouldSendEvent(thisEvent)) {
manager.onTimeZoneProviderEvent(thisEvent);
mLastEventSent = thisEvent;
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.aidl b/core/java/android/service/timezone/TimeZoneProviderStatus.aidl
new file mode 100644
index 000000000000..91dc7e99fd9a
--- /dev/null
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.service.timezone;
+
+/**
+ * @hide
+ */
+parcelable TimeZoneProviderStatus;
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
new file mode 100644
index 000000000000..87d7843bacaa
--- /dev/null
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -0,0 +1,336 @@
+/*
+ * 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 android.service.timezone;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+
+/**
+ * Information about the status of a {@link TimeZoneProviderService}.
+ *
+ * <p>Not all status properties or status values will apply to all provider implementations.
+ * {@code _NOT_APPLICABLE} status can be used to indicate properties that have no meaning for a
+ * given implementation.
+ *
+ * <p>Time zone providers are expected to work in one of two ways:
+ * <ol>
+ * <li>Location: Providers will determine location and then map that location to one or more
+ * time zone IDs.</li>
+ * <li>External signals: Providers could use indirect signals like country code
+ * and/or local offset / DST information provided to the device to infer a time zone, e.g.
+ * signals like MCC and NITZ for telephony devices, IP geo location, or DHCP information
+ * (RFC4833). The time zone ID could also be fed directly to the device by an external service.
+ * </li>
+ * </ol>
+ *
+ * <p>The status properties are:
+ * <ul>
+ * <li>location detection - for location-based providers, the status of the location detection
+ * mechanism</li>
+ * <li>connectivity - connectivity can influence providers directly, for example if they use
+ * a networked service to map location to time zone ID, or use geo IP, or indirectly for
+ * location detection (e.g. for the network location provider.</li>
+ * <li>time zone resolution - the status related to determining a time zone ID or using a
+ * detected time zone ID. For example, a networked service may be reachable (i.e. connectivity
+ * is working) but the service could return errors, a time zone ID detected may not be usable
+ * for a device because of TZDB version skew, or external indirect signals may available but
+ * do not match the properties of a known time zone ID.</li>
+ * </ul>
+ *
+ * @hide
+ */
+public final class TimeZoneProviderStatus implements Parcelable {
+
+ /**
+ * A status code related to a dependency a provider may have.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "DEPENDENCY_STATUS_", value = {
+ DEPENDENCY_STATUS_UNKNOWN,
+ DEPENDENCY_STATUS_NOT_APPLICABLE,
+ DEPENDENCY_STATUS_WORKING,
+ DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE,
+ DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT,
+ DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DependencyStatus {}
+
+ /** The dependency's status is unknown. */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_UNKNOWN = 0;
+
+ /** The dependency is not used by the provider's implementation. */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_NOT_APPLICABLE = 1;
+
+ /** The dependency is applicable and working well. */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_WORKING = 2;
+
+ /**
+ * The dependency is used but is temporarily unavailable, e.g. connectivity has been lost for an
+ * unpredictable amount of time.
+ *
+ * <p>This status is considered normal is may be entered many times a day.
+ */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3;
+
+ /**
+ * The dependency is used by the provider but is blocked by the environment in a way that the
+ * provider has detected and is considered likely to persist for some time, e.g. connectivity
+ * has been lost due to boarding a plane.
+ *
+ * <p>This status is considered unusual and could be used by the system as a trigger to try
+ * other time zone providers / time zone detection mechanisms. The bar for using this status
+ * should therefore be set fairly high to avoid a device bringing up other providers or
+ * switching to a different detection mechanism that may provide a different suggestion.
+ */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4;
+
+ /**
+ * The dependency is used by the provider but is running in a degraded mode due to the user's
+ * settings. A user can take action to improve this, e.g. by changing a setting.
+ *
+ * <p>This status could be used by the system as a trigger to try other time zone
+ * providers / time zone detection mechanisms. The user may be informed.
+ */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5;
+
+ /**
+ * The dependency is used by the provider but is completely blocked by the user's settings.
+ * A user can take action to correct this, e.g. by changing a setting.
+ *
+ * <p>This status could be used by the system as a trigger to try other time zone providers /
+ * time zone detection mechanisms. The user may be informed.
+ */
+ public static final @DependencyStatus int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6;
+
+ /**
+ * A status code related to an operation in a provider's detection algorithm.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "OPERATION_STATUS_", value = {
+ OPERATION_STATUS_UNKNOWN,
+ OPERATION_STATUS_NOT_APPLICABLE,
+ OPERATION_STATUS_WORKING,
+ OPERATION_STATUS_FAILED,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OperationStatus {}
+
+ /** The operation's status is unknown. */
+ public static final @OperationStatus int OPERATION_STATUS_UNKNOWN = 0;
+
+ /** The operation is not used by the provider's implementation. */
+ public static final @OperationStatus int OPERATION_STATUS_NOT_APPLICABLE = 1;
+
+ /** The operation is applicable and working well. */
+ public static final @OperationStatus int OPERATION_STATUS_WORKING = 2;
+
+ /** The operation is applicable and failed. */
+ public static final @OperationStatus int OPERATION_STATUS_FAILED = 3;
+
+ /**
+ * An instance that provides no information about status. Effectively a "null" status.
+ */
+ @NonNull
+ public static final TimeZoneProviderStatus UNKNOWN = new TimeZoneProviderStatus(
+ DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_UNKNOWN, OPERATION_STATUS_UNKNOWN);
+
+ private final @DependencyStatus int mLocationDetectionStatus;
+ private final @DependencyStatus int mConnectivityStatus;
+ private final @OperationStatus int mTimeZoneResolutionStatus;
+
+ private TimeZoneProviderStatus(
+ @DependencyStatus int locationDetectionStatus,
+ @DependencyStatus int connectivityStatus,
+ @OperationStatus int timeZoneResolutionStatus) {
+ mLocationDetectionStatus = requireValidDependencyStatus(locationDetectionStatus);
+ mConnectivityStatus = requireValidDependencyStatus(connectivityStatus);
+ mTimeZoneResolutionStatus = requireValidOperationStatus(timeZoneResolutionStatus);
+ }
+
+ /**
+ * Returns the status of the location detection dependencies used by the provider (where
+ * applicable).
+ */
+ public @DependencyStatus int getLocationDetectionStatus() {
+ return mLocationDetectionStatus;
+ }
+
+ /**
+ * Returns the status of the connectivity dependencies used by the provider (where applicable).
+ */
+ public @DependencyStatus int getConnectivityStatus() {
+ return mConnectivityStatus;
+ }
+
+ /**
+ * Returns the status of the time zone resolution operation used by the provider.
+ */
+ public @OperationStatus int getTimeZoneResolutionStatus() {
+ return mTimeZoneResolutionStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneProviderStatus{"
+ + "mLocationDetectionStatus=" + mLocationDetectionStatus
+ + ", mConnectivityStatus=" + mConnectivityStatus
+ + ", mTimeZoneResolutionStatus=" + mTimeZoneResolutionStatus
+ + '}';
+ }
+
+ public static final @NonNull Creator<TimeZoneProviderStatus> CREATOR = new Creator<>() {
+ @Override
+ public TimeZoneProviderStatus createFromParcel(Parcel in) {
+ @DependencyStatus int locationDetectionStatus = in.readInt();
+ @DependencyStatus int connectivityStatus = in.readInt();
+ @OperationStatus int timeZoneResolutionStatus = in.readInt();
+ return new TimeZoneProviderStatus(
+ locationDetectionStatus, connectivityStatus, timeZoneResolutionStatus);
+ }
+
+ @Override
+ public TimeZoneProviderStatus[] newArray(int size) {
+ return new TimeZoneProviderStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mLocationDetectionStatus);
+ parcel.writeInt(mConnectivityStatus);
+ parcel.writeInt(mTimeZoneResolutionStatus);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneProviderStatus that = (TimeZoneProviderStatus) o;
+ return mLocationDetectionStatus == that.mLocationDetectionStatus
+ && mConnectivityStatus == that.mConnectivityStatus
+ && mTimeZoneResolutionStatus == that.mTimeZoneResolutionStatus;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+ }
+
+ /** A builder for {@link TimeZoneProviderStatus}. */
+ public static final class Builder {
+
+ private @DependencyStatus int mLocationDetectionStatus = DEPENDENCY_STATUS_UNKNOWN;
+ private @DependencyStatus int mConnectivityStatus = DEPENDENCY_STATUS_UNKNOWN;
+ private @OperationStatus int mTimeZoneResolutionStatus = OPERATION_STATUS_UNKNOWN;
+
+ /**
+ * Creates a new builder instance. At creation time all status properties are set to
+ * their "UNKNOWN" value.
+ */
+ public Builder() {
+ }
+
+ /**
+ * @hide
+ */
+ public Builder(TimeZoneProviderStatus toCopy) {
+ mLocationDetectionStatus = toCopy.mLocationDetectionStatus;
+ mConnectivityStatus = toCopy.mConnectivityStatus;
+ mTimeZoneResolutionStatus = toCopy.mTimeZoneResolutionStatus;
+ }
+
+ /**
+ * Sets the status of the provider's location detection dependency (where applicable).
+ * See the {@code DEPENDENCY_STATUS_} constants for more information.
+ */
+ @NonNull
+ public Builder setLocationDetectionStatus(@DependencyStatus int locationDetectionStatus) {
+ mLocationDetectionStatus = locationDetectionStatus;
+ return this;
+ }
+
+ /**
+ * Sets the status of the provider's connectivity dependency (where applicable).
+ * See the {@code DEPENDENCY_STATUS_} constants for more information.
+ */
+ @NonNull
+ public Builder setConnectivityStatus(@DependencyStatus int connectivityStatus) {
+ mConnectivityStatus = connectivityStatus;
+ return this;
+ }
+
+ /**
+ * Sets the status of the provider's time zone resolution operation.
+ * See the {@code OPERATION_STATUS_} constants for more information.
+ */
+ @NonNull
+ public Builder setTimeZoneResolutionStatus(@OperationStatus int timeZoneResolutionStatus) {
+ mTimeZoneResolutionStatus = timeZoneResolutionStatus;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TimeZoneProviderStatus} instance.
+ */
+ @NonNull
+ public TimeZoneProviderStatus build() {
+ return new TimeZoneProviderStatus(
+ mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+ }
+ }
+
+ private @OperationStatus int requireValidOperationStatus(@OperationStatus int operationStatus) {
+ if (operationStatus < OPERATION_STATUS_UNKNOWN
+ || operationStatus > OPERATION_STATUS_FAILED) {
+ throw new IllegalArgumentException(Integer.toString(operationStatus));
+ }
+ return operationStatus;
+ }
+
+ private static @DependencyStatus int requireValidDependencyStatus(
+ @DependencyStatus int dependencyStatus) {
+ if (dependencyStatus < DEPENDENCY_STATUS_UNKNOWN
+ || dependencyStatus > DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) {
+ throw new IllegalArgumentException(Integer.toString(dependencyStatus));
+ }
+ return dependencyStatus;
+ }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 519fc55b523d..1337d6a87df8 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -36,7 +36,6 @@ import android.text.style.LineBackgroundSpan;
import android.text.style.ParagraphStyle;
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
-import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -1859,13 +1858,12 @@ public abstract class Layout {
* @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
* text segment
* @param inclusionStrategy strategy for determining whether a text segment is inside the
- * specified area
- * @return an integer range where the endpoints are the start (inclusive) and end (exclusive)
- * character offsets of the text range, or null if there are no text segments inside the
- * area
+ * specified area
+ * @return int array of size 2 containing the start (inclusive) and end (exclusive) character
+ * offsets of the text range, or null if there are no text segments inside the area
*/
@Nullable
- public Range<Integer> getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
+ public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
@NonNull TextInclusionStrategy inclusionStrategy) {
// Find the first line whose bottom (without line spacing) is below the top of the area.
int startLine = getLineForVertical((int) area.top);
@@ -1923,7 +1921,7 @@ public abstract class Layout {
start = segmentFinder.previousStartBoundary(start + 1);
end = segmentFinder.nextEndBoundary(end - 1);
- return new Range(start, end);
+ return new int[] {start, end};
}
/**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9789b5670fbb..06c1b258cb70 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2001,7 +2001,6 @@ public class KeyEvent extends InputEvent implements Parcelable {
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bfa13507ed50..efda257aed27 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -888,20 +888,18 @@ public final class ViewRootImpl implements ViewParent,
static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
new BLASTBufferQueue.TransactionHangCallback() {
@Override
- public void onTransactionHang(boolean isGPUHang) {
- if (isGPUHang && !sAnrReported) {
- sAnrReported = true;
- try {
- ActivityManager.getService().appNotResponding(
- "Buffer processing hung up due to stuck fence. Indicates GPU hang");
- } catch (RemoteException e) {
- // We asked the system to crash us, but the system
- // already crashed. Unfortunately things may be
- // out of control.
- }
- } else {
- // TODO: Do something with this later. For now we just ANR
- // in dequeue buffer later like we always have.
+ public void onTransactionHang(String reason) {
+ if (sAnrReported) {
+ return;
+ }
+
+ sAnrReported = true;
+ try {
+ ActivityManager.getService().appNotResponding(reason);
+ } catch (RemoteException e) {
+ // We asked the system to crash us, but the system
+ // already crashed. Unfortunately things may be
+ // out of control.
}
}
};
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 201efe81c102..69eed0abb1a6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -30,7 +30,9 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl
import static android.view.inputmethod.InputMethodManagerProto.ACTIVE;
import static android.view.inputmethod.InputMethodManagerProto.CUR_ID;
import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE;
+import static android.view.inputmethod.InputMethodManagerProto.NEXT_SERVED_VIEW;
import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING;
+import static android.view.inputmethod.InputMethodManagerProto.SERVED_VIEW;
import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS;
@@ -763,13 +765,11 @@ public final class InputMethodManager {
forceFocus = true;
}
}
- startInputOnWindowFocusGain(viewForWindowFocus,
- windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
- }
- private void startInputOnWindowFocusGain(View focusedView,
- @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
- int startInputFlags = getStartInputFlags(focusedView, 0);
+ final int softInputMode = windowAttribute.softInputMode;
+ final int windowFlags = windowAttribute.flags;
+
+ int startInputFlags = getStartInputFlags(viewForWindowFocus, 0);
startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
ImeTracing.getInstance().triggerClientDump(
@@ -784,9 +784,9 @@ public final class InputMethodManager {
if (mRestartOnNextWindowFocus) {
if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus as true");
mRestartOnNextWindowFocus = false;
- forceNewFocus = true;
+ forceFocus = true;
}
- checkFocusResult = checkFocusInternalLocked(forceNewFocus, mCurRootView);
+ checkFocusResult = checkFocusInternalLocked(forceFocus, mCurRootView);
}
if (checkFocusResult) {
@@ -795,7 +795,7 @@ public final class InputMethodManager {
// about the window gaining focus, to help make the transition
// smooth.
if (startInputOnWindowFocusGainInternal(StartInputReason.WINDOW_FOCUS_GAIN,
- focusedView, startInputFlags, softInputMode, windowFlags)) {
+ viewForWindowFocus, startInputFlags, softInputMode, windowFlags)) {
return;
}
}
@@ -810,7 +810,7 @@ public final class InputMethodManager {
// ignore the result
mServiceInvoker.startInputOrWindowGainedFocus(
StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
- focusedView.getWindowToken(), startInputFlags, softInputMode,
+ viewForWindowFocus.getWindowToken(), startInputFlags, softInputMode,
windowFlags,
null,
null, null,
@@ -903,8 +903,6 @@ public final class InputMethodManager {
/**
* Checks whether the active input connection (if any) is for the given view.
*
- * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic.
- *
* Note that this method is only intended for restarting input after focus gain
* (e.g. b/160391516), DO NOT leverage this method to do another check.
*/
@@ -915,7 +913,6 @@ public final class InputMethodManager {
}
return mServedInputConnection != null
- && mServedInputConnection.isActive()
&& mServedInputConnection.isAssociatedWith(view);
}
}
@@ -1128,10 +1125,13 @@ public final class InputMethodManager {
if (!checkFocusInternalLocked(mRestartOnNextWindowFocus, mCurRootView)) {
return;
}
- final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
- : StartInputReason.DEACTIVATED_BY_IMMS;
- startInputOnWindowFocusGainInternal(reason, null, 0, 0, 0);
+ mCurrentEditorInfo = null;
+ mCompletions = null;
+ mServedConnecting = true;
}
+ final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
+ : StartInputReason.DEACTIVATED_BY_IMMS;
+ startInputInner(reason, null, 0, 0, 0);
return;
}
case MSG_SET_INTERACTIVE: {
@@ -3992,6 +3992,8 @@ public final class InputMethodManager {
proto.write(FULLSCREEN_MODE, mFullscreenMode);
proto.write(ACTIVE, mActive);
proto.write(SERVED_CONNECTING, mServedConnecting);
+ proto.write(SERVED_VIEW, Objects.toString(mServedView));
+ proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
proto.end(token);
if (mCurRootView != null) {
mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL);
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index fa18eeceafd0..f2b70997de63 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -214,7 +214,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
}
- public boolean isActive() {
+ private boolean isActive() {
return mParentInputMethodManager.isActive() && !isFinished();
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 57103e4955ca..b5c58fb4bfc0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -151,7 +151,6 @@ import android.util.DisplayMetrics;
import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
-import android.util.Range;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.AccessibilityIterators.TextSegmentIterator;
@@ -9317,40 +9316,42 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/** @hide */
public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
- Range<Integer> range = getRangeForRect(
+ int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
gesture.getGranularity());
if (range == null) {
return handleGestureFailure(gesture);
}
- Selection.setSelection(getEditableText(), range.getLower(), range.getUpper());
+ Selection.setSelection(getEditableText(), range[0], range[1]);
mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
- Range<Integer> startRange = getRangeForRect(
+ int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
gesture.getGranularity());
if (startRange == null) {
return handleGestureFailure(gesture);
}
- Range<Integer> endRange = getRangeForRect(
+ int[] endRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
gesture.getGranularity());
- if (endRange == null || endRange.getUpper() <= startRange.getLower()) {
+ if (endRange == null) {
return handleGestureFailure(gesture);
}
- Range<Integer> range = startRange.extend(endRange);
- Selection.setSelection(getEditableText(), range.getLower(), range.getUpper());
+ int[] range = new int[] {
+ Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
+ };
+ Selection.setSelection(getEditableText(), range[0], range[1]);
mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
- Range<Integer> range = getRangeForRect(
+ int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
gesture.getGranularity());
if (range == null) {
@@ -9361,42 +9362,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
range = adjustHandwritingDeleteGestureRange(range);
}
- getEditableText().delete(range.getLower(), range.getUpper());
- Selection.setSelection(getEditableText(), range.getLower());
+ getEditableText().delete(range[0], range[1]);
+ Selection.setSelection(getEditableText(), range[0]);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
- Range<Integer> startRange = getRangeForRect(
+ int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
gesture.getGranularity());
if (startRange == null) {
return handleGestureFailure(gesture);
}
- Range<Integer> endRange = getRangeForRect(
+ int[] endRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
gesture.getGranularity());
if (endRange == null) {
return handleGestureFailure(gesture);
}
- Range<Integer> range = startRange.extend(endRange);
+ int[] range = new int[] {
+ Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
+ };
if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
range = adjustHandwritingDeleteGestureRange(range);
}
- getEditableText().delete(range.getLower(), range.getUpper());
- Selection.setSelection(getEditableText(), range.getLower());
+ getEditableText().delete(range[0], range[1]);
+ Selection.setSelection(getEditableText(), range[0]);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
- private Range<Integer> adjustHandwritingDeleteGestureRange(Range<Integer> range) {
+ private int[] adjustHandwritingDeleteGestureRange(int[] range) {
// For handwriting delete gestures with word granularity, adjust the start and end offsets
// to remove extra whitespace around the deleted text.
- int start = range.getLower();
- int end = range.getUpper();
+ int start = range[0];
+ int end = range[1];
// If the deleted text is at the start of the text, the behavior is the same as the case
// where the deleted text follows a new line character.
@@ -9425,7 +9428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (start == 0) break;
codePointBeforeStart = Character.codePointBefore(mText, start);
} while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
- return new Range(start, end);
+ return new int[] {start, end};
}
if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
@@ -9444,7 +9447,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (end == mText.length()) break;
codePointAtEnd = Character.codePointAt(mText, end);
} while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
- return new Range(start, end);
+ return new int[] {start, end};
}
// Return the original range.
@@ -9494,14 +9497,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
lineVerticalCenter + 0.1f,
Math.max(startPoint.x, endPoint.x),
lineVerticalCenter - 0.1f);
- Range<Integer> range = mLayout.getRangeForRect(
+ int[] range = mLayout.getRangeForRect(
area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
if (range == null) {
return handleGestureFailure(gesture);
}
- int startOffset = range.getLower();
- int endOffset = range.getUpper();
+ int startOffset = range[0];
+ int endOffset = range[1];
// TODO(b/247557062): This doesn't handle bidirectional text correctly.
Pattern whitespacePattern = getWhitespacePattern();
@@ -9606,7 +9609,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Nullable
- private Range<Integer> getRangeForRect(@NonNull RectF area, int granularity) {
+ private int[] getRangeForRect(@NonNull RectF area, int granularity) {
SegmentFinder segmentFinder;
if (granularity == HandwritingGesture.GRANULARITY_WORD) {
WordIterator wordIterator = getWordIterator();
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 4a4f561c71ed..85b288113b45 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -18,8 +18,10 @@ package android.window;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,8 @@ public class BackEvent implements Parcelable {
@SwipeEdge
private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackEvent} instance.
@@ -58,12 +62,16 @@ public class BackEvent implements Parcelable {
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
}
private BackEvent(@NonNull Parcel in) {
@@ -71,6 +79,7 @@ public class BackEvent implements Parcelable {
mTouchY = in.readFloat();
mProgress = in.readFloat();
mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
@@ -96,6 +105,7 @@ public class BackEvent implements Parcelable {
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
@@ -126,6 +136,16 @@ public class BackEvent implements Parcelable {
return mSwipeEdge;
}
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
@Override
public String toString() {
return "BackEvent{"
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
new file mode 100644
index 000000000000..2e3afde1a78a
--- /dev/null
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.window;
+
+import android.util.FloatProperty;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+
+/**
+ * An animator that drives the predictive back progress with a spring.
+ *
+ * The back gesture's latest touch point and committal state determines the final position of
+ * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with
+ * smoothly transitioning progress values.
+ *
+ * @hide
+ */
+public class BackProgressAnimator {
+ /**
+ * A factor to scale the input progress by, so that it works better with the spring.
+ * We divide the output progress by this value before sending it to apps, so that apps
+ * always receive progress values in [0, 1].
+ */
+ private static final float SCALE_FACTOR = 100f;
+ private final SpringAnimation mSpring;
+ private ProgressCallback mCallback;
+ private float mProgress = 0;
+ private BackEvent mLastBackEvent;
+ private boolean mStarted = false;
+
+ private void setProgress(float progress) {
+ mProgress = progress;
+ }
+
+ private float getProgress() {
+ return mProgress;
+ }
+
+ private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP =
+ new FloatProperty<BackProgressAnimator>("progress") {
+ @Override
+ public void setValue(BackProgressAnimator animator, float value) {
+ animator.setProgress(value);
+ animator.updateProgressValue(value);
+ }
+
+ @Override
+ public Float get(BackProgressAnimator object) {
+ return object.getProgress();
+ }
+ };
+
+
+ /** A callback to be invoked when there's a progress value update from the animator. */
+ public interface ProgressCallback {
+ /** Called when there's a progress value update. */
+ void onProgressUpdate(BackEvent event);
+ }
+
+ public BackProgressAnimator() {
+ mSpring = new SpringAnimation(this, PROGRESS_PROP);
+ mSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ }
+
+ /**
+ * Sets a new target position for the back progress.
+ *
+ * @param event the {@link BackEvent} containing the latest target progress.
+ */
+ public void onBackProgressed(BackEvent event) {
+ if (!mStarted) {
+ return;
+ }
+ mLastBackEvent = event;
+ if (mSpring == null) {
+ return;
+ }
+ mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR);
+ }
+
+ /**
+ * Starts the back progress animation.
+ *
+ * @param event the {@link BackEvent} that started the gesture.
+ * @param callback the back callback to invoke for the gesture. It will receive back progress
+ * dispatches as the progress animation updates.
+ */
+ public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ reset();
+ mLastBackEvent = event;
+ mCallback = callback;
+ mStarted = true;
+ }
+
+ /**
+ * Resets the back progress animation. This should be called when back is invoked or cancelled.
+ */
+ public void reset() {
+ mSpring.animateToFinalPosition(0);
+ if (mSpring.canSkipToEnd()) {
+ mSpring.skipToEnd();
+ } else {
+ // Should never happen.
+ mSpring.cancel();
+ }
+ mStarted = false;
+ mLastBackEvent = null;
+ mCallback = null;
+ mProgress = 0;
+ }
+
+ private void updateProgressValue(float progress) {
+ if (mLastBackEvent == null || mCallback == null || !mStarted) {
+ return;
+ }
+ mCallback.onProgressUpdate(
+ new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
+ mLastBackEvent.getDepartingAnimationTarget()));
+ }
+
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 47796de11dd5..6af8ddda3a62 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -28,17 +28,18 @@ import android.window.BackEvent;
oneway interface IOnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
- * Wraps {@link OnBackInvokedCallback#onBackStarted()}.
+ * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or button press.
*/
- void onBackStarted();
+ void onBackStarted(in BackEvent backEvent);
/**
* Called on back gesture progress.
- * Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
+ * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param touchX Absolute X location of the touch point.
- * @param touchY Absolute Y location of the touch point.
- * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param backEvent The {@link BackEvent} containing information about the latest touch point
+ * and the progress that the back animation should seek to.
*/
void onBackProgressed(in BackEvent backEvent);
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index 1a37e57df403..c05809bca4a5 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -13,14 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.window;
-
import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
-
/**
* Interface for applications to register back animation callbacks along their custom back
* handling.
@@ -40,11 +37,10 @@ import android.view.View;
* @hide
*/
public interface OnBackAnimationCallback extends OnBackInvokedCallback {
- /**
- * Called when a back gesture has been started, or back button has been pressed down.
- */
+ /**
+ * Called when a back gesture has been started, or back button has been pressed down.
+ */
default void onBackStarted() { }
-
/**
* Called on back gesture progress.
*
@@ -53,7 +49,6 @@ public interface OnBackAnimationCallback extends OnBackInvokedCallback {
* @see BackEvent
*/
default void onBackProgressed(@NonNull BackEvent backEvent) { }
-
/**
* Called when a back gesture or back button press has been cancelled.
*/
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 6e2d4f9edbc1..62c41bfb0681 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,6 +16,7 @@
package android.window;
+import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
import android.view.Window;
@@ -41,8 +42,35 @@ import android.view.Window;
@SuppressWarnings("deprecation")
public interface OnBackInvokedCallback {
/**
+ * Called when a back gesture has been started, or back button has been pressed down.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or
+ * button press.
+ *
+ * @hide
+ */
+ default void onBackStarted(@NonNull BackEvent backEvent) {}
+
+ /**
+ * Called when a back gesture has been progressed.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the latest touch point
+ * and the progress that the back animation should seek to.
+ *
+ * @hide
+ */
+ default void onBackProgressed(@NonNull BackEvent backEvent) {}
+
+ /**
* Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed.
*/
void onBackInvoked();
+
+ /**
+ * Called when a back gesture or button press has been cancelled.
+ *
+ * @hide
+ */
+ default void onBackCancelled() {}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0730f3ddf8ac..fda39c14dac7 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -218,19 +218,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public Checker getChecker() {
return mChecker;
}
+ @NonNull
+ private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
+
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
mCallback = new WeakReference<>(callback);
}
@Override
- public void onBackStarted() {
+ public void onBackStarted(BackEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- callback.onBackStarted();
+ mProgressAnimator.onBackStarted(backEvent, event ->
+ callback.onBackProgressed(event));
+ callback.onBackStarted(backEvent);
}
});
}
@@ -240,7 +245,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- callback.onBackProgressed(backEvent);
+ mProgressAnimator.onBackProgressed(backEvent);
}
});
}
@@ -248,6 +253,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackCancelled() {
Handler.getMain().post(() -> {
+ mProgressAnimator.reset();
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
callback.onBackCancelled();
@@ -258,6 +264,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> {
+ mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallback.get();
if (callback == null) {
return;
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 4a1f7eb06c40..42b46cda6ba3 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -647,15 +647,16 @@ public class ResolverListAdapter extends BaseAdapter {
if (info instanceof DisplayResolveInfo) {
DisplayResolveInfo dri = (DisplayResolveInfo) info;
- boolean hasLabel = dri.hasDisplayLabel();
- holder.bindLabel(
- dri.getDisplayLabel(),
- dri.getExtendedInfo(),
- hasLabel && alwaysShowSubLabel());
- holder.bindIcon(info);
- if (!hasLabel) {
+ if (dri.hasDisplayLabel()) {
+ holder.bindLabel(
+ dri.getDisplayLabel(),
+ dri.getExtendedInfo(),
+ alwaysShowSubLabel());
+ } else {
+ holder.bindLabel("", "", false);
loadLabel(dri);
}
+ holder.bindIcon(info);
if (!dri.hasDisplayIcon()) {
loadIcon(dri);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index e74898294c09..8e7207fa91ee 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -45,6 +45,7 @@ interface IAppWidgetService {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId);
int[] getAppWidgetIdsForHost(String callingPackage, int hostId);
+ void setAppWidgetHidden(in String callingPackage, int hostId);
IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
int intentFlags);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 76f33a6c3937..b0d59226d4ec 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -45,6 +45,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
@@ -224,6 +225,7 @@ public class InteractionJankMonitor {
public static final int CUJ_SHADE_CLEAR_ALL = 62;
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+ public static final int CUJ_RECENTS_SCROLLING = 65;
private static final int NO_STATSD_LOGGING = -1;
@@ -297,6 +299,7 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
};
private static class InstanceHolder {
@@ -385,7 +388,8 @@ public class InteractionJankMonitor {
CUJ_TASKBAR_COLLAPSE,
CUJ_SHADE_CLEAR_ALL,
CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
- CUJ_LOCKSCREEN_OCCLUSION
+ CUJ_LOCKSCREEN_OCCLUSION,
+ CUJ_RECENTS_SCROLLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -900,6 +904,8 @@ public class InteractionJankMonitor {
return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
case CUJ_LOCKSCREEN_OCCLUSION:
return "LOCKSCREEN_OCCLUSION";
+ case CUJ_RECENTS_SCROLLING:
+ return "RECENTS_SCROLLING";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index a09c8236b47d..04dd2d72729d 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -97,7 +97,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
@@ -224,7 +223,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
}
case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 295dc545ef4b..25ac1bd678c6 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -41,12 +41,16 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorSpace;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
import android.os.SystemProperties;
import android.util.Slog;
+import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
import android.view.WindowManager.TransitionType;
@@ -59,9 +63,11 @@ import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
+import android.window.ScreenCapture;
import com.android.internal.R;
+import java.nio.ByteBuffer;
import java.util.List;
/** @hide */
@@ -1262,4 +1268,90 @@ public class TransitionAnimation {
return set;
}
+
+ /** Returns whether the hardware buffer passed in is marked as protected. */
+ public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
+ return (hardwareBuffer.getUsage() & HardwareBuffer.USAGE_PROTECTED_CONTENT)
+ == HardwareBuffer.USAGE_PROTECTED_CONTENT;
+ }
+
+ /** Returns the luminance in 0~1. */
+ public static float getBorderLuma(SurfaceControl surfaceControl, int w, int h) {
+ final ScreenCapture.ScreenshotHardwareBuffer buffer =
+ ScreenCapture.captureLayers(surfaceControl, new Rect(0, 0, w, h), 1);
+ if (buffer != null) {
+ return getBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
+ }
+ return 0;
+ }
+
+ /** Returns the luminance in 0~1. */
+ public static float getBorderLuma(HardwareBuffer hwBuffer, ColorSpace colorSpace) {
+ if (hwBuffer == null) {
+ return 0;
+ }
+ final int format = hwBuffer.getFormat();
+ // Only support RGB format in 4 bytes. And protected buffer is not readable.
+ if (format != HardwareBuffer.RGBA_8888 || hasProtectedContent(hwBuffer)) {
+ return 0;
+ }
+
+ final ImageReader ir = ImageReader.newInstance(hwBuffer.getWidth(), hwBuffer.getHeight(),
+ format, 1 /* maxImages */);
+ ir.getSurface().attachAndQueueBufferWithColorSpace(hwBuffer, colorSpace);
+ final Image image = ir.acquireLatestImage();
+ if (image == null || image.getPlaneCount() < 1) {
+ return 0;
+ }
+
+ final Image.Plane plane = image.getPlanes()[0];
+ final ByteBuffer buffer = plane.getBuffer();
+ final int width = image.getWidth();
+ final int height = image.getHeight();
+ final int pixelStride = plane.getPixelStride();
+ final int rowStride = plane.getRowStride();
+ final int sampling = 10;
+ final int[] borderLumas = new int[(width + height) * 2 / sampling];
+
+ // Grab the top and bottom borders.
+ int i = 0;
+ for (int x = 0, size = width - sampling; x < size; x += sampling) {
+ borderLumas[i++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
+ borderLumas[i++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
+ }
+
+ // Grab the left and right borders.
+ for (int y = 0, size = height - sampling; y < size; y += sampling) {
+ borderLumas[i++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
+ borderLumas[i++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
+ }
+
+ ir.close();
+
+ // Get "mode" by histogram.
+ final int[] histogram = new int[256];
+ int maxCount = 0;
+ int mostLuma = 0;
+ for (int luma : borderLumas) {
+ final int count = ++histogram[luma];
+ if (count > maxCount) {
+ maxCount = count;
+ mostLuma = luma;
+ }
+ }
+ return mostLuma / 255f;
+ }
+
+ /** Returns the luminance of the pixel in 0~255. */
+ private static int getPixelLuminance(ByteBuffer buffer, int x, int y, int pixelStride,
+ int rowStride) {
+ final int color = buffer.getInt(y * rowStride + x * pixelStride);
+ // The buffer from ImageReader is always in native order (little-endian), so extract the
+ // color components in reversed order.
+ final int r = color & 0xff;
+ final int g = (color >> 8) & 0xff;
+ final int b = (color >> 16) & 0xff;
+ // Approximation of WCAG 2.0 relative luminance.
+ return ((r * 8) + (g * 22) + (b * 2)) >> 5;
+ }
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 44cfe1aa4a79..1d4b246de5c8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -322,4 +322,7 @@ oneway interface IStatusBar
/** Unregisters a nearby media devices provider. */
void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
+
+ /** Dump protos from SystemUI. The proto definition is defined there */
+ void dumpProto(in String[] args, in ParcelFileDescriptor pfd);
}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 1520ea5c6831..03815108f6dd 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -71,10 +71,12 @@ public:
}
}
- void onTransactionHang(bool isGpuHang) {
+ void onTransactionHang(const std::string& reason) {
if (mTransactionHangObject) {
+ JNIEnv* env = getenv(mVm);
+ ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str()));
getenv(mVm)->CallVoidMethod(mTransactionHangObject,
- gTransactionHangCallback.onTransactionHang, isGpuHang);
+ gTransactionHangCallback.onTransactionHang, jReason.get());
}
}
@@ -177,7 +179,7 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo
sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
}
-
+
static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr,
jobject transactionHangCallback) {
sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
@@ -186,9 +188,8 @@ static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong pt
} else {
sp<TransactionHangCallbackWrapper> wrapper =
new TransactionHangCallbackWrapper{env, transactionHangCallback};
- queue->setTransactionHangCallback([wrapper](bool isGpuHang) {
- wrapper->onTransactionHang(isGpuHang);
- });
+ queue->setTransactionHangCallback(
+ [wrapper](const std::string& reason) { wrapper->onTransactionHang(reason); });
}
}
@@ -236,7 +237,8 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
jclass transactionHangClass =
FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback");
gTransactionHangCallback.onTransactionHang =
- GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V");
+ GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang",
+ "(Ljava/lang/String;)V");
return 0;
}
diff --git a/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp b/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp
index 09f3a727d16e..2437a511238c 100644
--- a/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp
+++ b/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp
@@ -89,42 +89,39 @@ static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
extern "C" {
-static jint SurfaceUtils_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeDetectSurfaceDataspace");
+static jint SurfaceUtils_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
+ ALOGV("nativeDetectSurfaceType");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
int32_t fmt = 0;
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
if (err != NO_ERROR) {
- ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
- err);
+ ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
+ strerror(-err), err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
return fmt;
}
-static jint SurfaceUtils_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeDetectSurfaceType");
+static jint SurfaceUtils_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
+ ALOGV("nativeDetectSurfaceDataspace");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
return BAD_VALUE;
}
- int32_t halFmt = 0;
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &halFmt);
+ int32_t fmt = 0;
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
if (err != NO_ERROR) {
- ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
- strerror(-err), err);
+ ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
+ err);
OVERRIDE_SURFACE_ERROR(err);
return err;
}
- int32_t dataspace = SurfaceUtils_nativeDetectSurfaceDataspace(env, thiz, surface);
- int32_t fmt = static_cast<int32_t>(
- mapHalFormatDataspaceToPublicFormat(halFmt, static_cast<android_dataspace>(dataspace)));
return fmt;
}
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 40f6e4f63cd7..5c71f692b80f 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -580,6 +580,7 @@ void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail
// TODO(narayan): This will be an error in a future android release.
// error = true;
// ALOGW("Zygote closed file descriptor %d.", it->first);
+ delete it->second;
it = open_fd_map_.erase(it);
} else {
// The entry from the file descriptor table is still open. Restat
diff --git a/core/proto/android/view/imefocuscontroller.proto b/core/proto/android/view/imefocuscontroller.proto
index ff9dee69207b..ccde9b7a7966 100644
--- a/core/proto/android/view/imefocuscontroller.proto
+++ b/core/proto/android/view/imefocuscontroller.proto
@@ -25,6 +25,6 @@ option java_multiple_files = true;
*/
message ImeFocusControllerProto {
optional bool has_ime_focus = 1;
- optional string served_view = 2;
- optional string next_served_view = 3;
+ optional string served_view = 2 [deprecated = true];
+ optional string next_served_view = 3 [deprecated = true];
} \ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodmanager.proto b/core/proto/android/view/inputmethod/inputmethodmanager.proto
index 9fed0ef95a27..ea5f1e8f3be2 100644
--- a/core/proto/android/view/inputmethod/inputmethodmanager.proto
+++ b/core/proto/android/view/inputmethod/inputmethodmanager.proto
@@ -29,4 +29,6 @@ message InputMethodManagerProto {
optional int32 display_id = 3;
optional bool active = 4;
optional bool served_connecting = 5;
+ optional string served_view = 6;
+ optional string next_served_view = 7;
} \ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6ce31fc13f85..554b15374943 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1151,7 +1151,28 @@
android:protectionLevel="dangerous" />
<!-- Allows an application to write to external storage.
- <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
+ higher, this permission has no effect.
+
+ <p>If your app is on a device that runs API level 19 or higher, you don't need to declare
+ this permission to read and write files in your application-specific directories returned
+ by {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.
+
+ <p>Learn more about how to
+ <a href="{@docRoot}training/data-storage/shared/media#update-other-apps-files">modify media
+ files</a> that your app doesn't own, and how to
+ <a href="{@docRoot}training/data-storage/shared/documents-files">modify non-media files</a>
+ that your app doesn't own.
+
+ <p>If your app is a file manager and needs broad access to external storage files, then
+ the system must place your app on an allowlist so that you can successfully request the
+ <a href="#MANAGE_EXTERNAL_STORAGE><code>MANAGE_EXTERNAL_STORAGE</code></a> permission.
+ Learn more about the appropriate use cases for
+ <a href="{@docRoot}training/data-storage/manage-all-files>managing all files on a storage
+ device</a>.
+
+ <p>If <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
minSdkVersion}</a> and <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
@@ -1159,12 +1180,6 @@
grants your app this permission. If you don't need this permission, be sure your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
targetSdkVersion}</a> is 4 or higher.
- <p>Starting in API level 19, this permission is <em>not</em> required to
- read/write files in your application-specific directories returned by
- {@link android.content.Context#getExternalFilesDir} and
- {@link android.content.Context#getExternalCacheDir}.
- <p>If this permission is not allowlisted for an app that targets an API level before
- {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
<p>Protection level: dangerous</p>
-->
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
@@ -3042,6 +3057,12 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows an application to set user association
+ with a certain subscription. Used by Enterprise to associate a
+ subscription with a work or personal profile. -->
+ <permission android:name="android.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
device. -->
<permission android:name="android.permission.QUERY_USERS"
@@ -3155,6 +3176,13 @@
<!-- Allows an application to call
{@link android.app.ActivityManager#killBackgroundProcesses}.
+ <p>As of Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ the {@link android.app.ActivityManager#killBackgroundProcesses} is no longer available to
+ third party applications. For backwards compatibility, the background processes of the
+ caller's own package will still be killed when calling this API. If the caller has
+ the system permission {@code KILL_ALL_BACKGROUND_PROCESSES}, other processes will be
+ killed too.
+
<p>Protection level: normal
-->
<permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"
@@ -3162,6 +3190,16 @@
android:description="@string/permdesc_killBackgroundProcesses"
android:protectionLevel="normal" />
+ <!-- @SystemApi @hide Allows an application to call
+ {@link android.app.ActivityManager#killBackgroundProcesses}
+ to kill background processes of other apps.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.KILL_ALL_BACKGROUND_PROCESSES"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allows an application to query process states and current
OOM adjustment scores.
<p>Not for use by third-party applications. -->
@@ -4260,6 +4298,13 @@
<permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a CredentialProviderService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Alternative version of android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE.
This permission was renamed during the O previews but it was supported on the final O
release, so we need to carry it over.
@@ -6570,6 +6615,13 @@
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
+ <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
+ <p>Protection level: internal|role
+ <p>Intended for use by the FINANCED_DEVICE_KIOSK role only.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
+ android:protectionLevel="internal|role" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/anim/dream_activity_close_exit.xml b/core/res/res/anim/dream_activity_close_exit.xml
index c4599dad31a0..8df624fdd2e5 100644
--- a/core/res/res/anim/dream_activity_close_exit.xml
+++ b/core/res/res/anim/dream_activity_close_exit.xml
@@ -19,5 +19,5 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="100" />
+ android:duration="@integer/config_dreamCloseAnimationDuration" />
diff --git a/core/res/res/anim/dream_activity_open_enter.xml b/core/res/res/anim/dream_activity_open_enter.xml
index 9e1c6e2ee0d7..d6d9c5c990f8 100644
--- a/core/res/res/anim/dream_activity_open_enter.xml
+++ b/core/res/res/anim/dream_activity_open_enter.xml
@@ -22,5 +22,5 @@ those two has to be the same. -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.0"
android:toAlpha="1.0"
- android:duration="1000" />
+ android:duration="@integer/config_dreamOpenAnimationDuration" />
diff --git a/core/res/res/anim/dream_activity_open_exit.xml b/core/res/res/anim/dream_activity_open_exit.xml
index 740f52856b7f..2c2e501eda69 100644
--- a/core/res/res/anim/dream_activity_open_exit.xml
+++ b/core/res/res/anim/dream_activity_open_exit.xml
@@ -22,4 +22,4 @@ dream_activity_open_enter animation. -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="1.0"
- android:duration="1000" />
+ android:duration="@integer/config_dreamOpenAnimationDuration" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index caa67de1fe61..173908d97b56 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2447,6 +2447,11 @@
<!-- Whether dreams are disabled when ambient mode is suppressed. -->
<bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
+ <!-- The duration in milliseconds of the dream opening animation. -->
+ <integer name="config_dreamOpenAnimationDuration">250</integer>
+ <!-- The duration in milliseconds of the dream closing animation. -->
+ <integer name="config_dreamCloseAnimationDuration">100</integer>
+
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
<bool name="config_dismissDreamOnActivityStart">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d0fca8b0a7bb..509de3364f0e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -202,6 +202,11 @@
<!-- Displayed to tell the user that they cannot change the caller ID setting. -->
<string name="CLIRPermanent">You can\'t change the caller ID setting.</string>
+ <!-- Notification title to tell the user that auto data switch has occurred. [CHAR LIMIT=NOTIF_TITLE] -->
+ <string name="auto_data_switch_title">Switched data to <xliff:g id="carrierDisplay" example="Verizon">%s</xliff:g></string>
+ <!-- Notification content to tell the user that auto data switch can be disabled at settings. [CHAR LIMIT=NOTIF_BODY] -->
+ <string name="auto_data_switch_content">You can change this anytime in Settings</string>
+
<!-- Notification title to tell the user that data service is blocked by access control. [CHAR LIMIT=NOTIF_TITLE] -->
<string name="RestrictedOnDataTitle">No mobile data service</string>
<!-- Notification title to tell the user that emergency calling is blocked by access control. [CHAR LIMIT=NOTIF_TITLE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fc55ed2fe443..476d36d0c207 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -602,6 +602,8 @@
<java-symbol type="string" name="RestrictedOnEmergencyTitle" />
<java-symbol type="string" name="RestrictedOnNormalTitle" />
<java-symbol type="string" name="RestrictedStateContent" />
+ <java-symbol type="string" name="auto_data_switch_title" />
+ <java-symbol type="string" name="auto_data_switch_content" />
<java-symbol type="string" name="RestrictedStateContentMsimTemplate" />
<java-symbol type="string" name="notification_channel_network_alert" />
<java-symbol type="string" name="notification_channel_call_forward" />
@@ -2236,6 +2238,8 @@
<java-symbol type="string" name="config_dreamsDefaultComponent" />
<java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
<java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+ <java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
+ <java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
<java-symbol type="array" name="config_supportedDreamComplications" />
<java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
@@ -3723,7 +3727,6 @@
<java-symbol type="integer" name="config_maxUiWidth" />
<!-- system notification channels -->
- <java-symbol type="string" name="notification_channel_virtual_keyboard" />
<java-symbol type="string" name="notification_channel_physical_keyboard" />
<java-symbol type="string" name="notification_channel_security" />
<java-symbol type="string" name="notification_channel_car_mode" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
new file mode 100644
index 000000000000..42143b92e9d8
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.hardware.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramSelector;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class RadioAnnouncementTest {
+ private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 90500);
+ private static final ProgramSelector FM_PROGRAM_SELECTOR = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER, /* secondaryIds= */ null,
+ /* vendorIds= */ null);
+ private static final int TRAFFIC_ANNOUNCEMENT_TYPE = Announcement.TYPE_TRAFFIC;
+ private static final Map<String, String> VENDOR_INFO = createVendorInfo();
+ private static final Announcement TEST_ANNOUNCEMENT =
+ new Announcement(FM_PROGRAM_SELECTOR, TRAFFIC_ANNOUNCEMENT_TYPE, VENDOR_INFO);
+
+ @Test
+ public void constructor_withNullSelector_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ new Announcement(/* selector= */ null, TRAFFIC_ANNOUNCEMENT_TYPE, VENDOR_INFO);
+ });
+
+ assertWithMessage("Exception for null program selector in announcement constructor")
+ .that(thrown).hasMessageThat().contains("Program selector cannot be null");
+ }
+
+ @Test
+ public void constructor_withNullVendorInfo_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ new Announcement(FM_PROGRAM_SELECTOR, TRAFFIC_ANNOUNCEMENT_TYPE,
+ /* vendorInfo= */ null);
+ });
+
+ assertWithMessage("Exception for null vendor info in announcement constructor")
+ .that(thrown).hasMessageThat().contains("Vendor info cannot be null");
+ }
+
+ @Test
+ public void getSelector() {
+ assertWithMessage("Radio announcement selector")
+ .that(TEST_ANNOUNCEMENT.getSelector()).isEqualTo(FM_PROGRAM_SELECTOR);
+ }
+
+ @Test
+ public void getType() {
+ assertWithMessage("Radio announcement type")
+ .that(TEST_ANNOUNCEMENT.getType()).isEqualTo(TRAFFIC_ANNOUNCEMENT_TYPE);
+ }
+
+ @Test
+ public void getVendorInfo() {
+ assertWithMessage("Radio announcement vendor info")
+ .that(TEST_ANNOUNCEMENT.getVendorInfo()).isEqualTo(VENDOR_INFO);
+ }
+
+ private static Map<String, String> createVendorInfo() {
+ Map<String, String> vendorInfo = new ArrayMap<>();
+ vendorInfo.put("vendorKeyMock", "vendorValueMock");
+ return vendorInfo;
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 259a11852784..9bfa2fba6948 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -20,9 +20,12 @@ import static com.google.common.truth.Truth.assertWithMessage;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
import org.junit.Test;
+import java.util.Arrays;
+
public final class RadioManagerTest {
private static final int REGION = RadioManager.REGION_ITU_2;
@@ -63,6 +66,32 @@ public final class RadioManagerTest {
private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig();
private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties();
+ /**
+ * Info flags with live, tuned and stereo enabled
+ */
+ private static final int INFO_FLAGS = 0b110001;
+ private static final int SIGNAL_QUALITY = 2;
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x10000111);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_RELATED =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x10000113);
+ private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1013);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 95500);
+ private static final ProgramSelector DAB_SELECTOR =
+ new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER,
+ new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER},
+ /* vendorIds= */ null);
+ private static final RadioMetadata METADATA = createMetadata();
+ private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO =
+ createDabProgramInfo(DAB_SELECTOR);
+
@Test
public void getType_forBandDescriptor() {
RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -460,6 +489,123 @@ public final class RadioManagerTest {
.that(AMFM_PROPERTIES).isNotEqualTo(propertiesDab);
}
+ @Test
+ public void getSelector_forProgramInfo() {
+ assertWithMessage("Selector of DAB program info")
+ .that(DAB_PROGRAM_INFO.getSelector()).isEqualTo(DAB_SELECTOR);
+ }
+
+ @Test
+ public void getLogicallyTunedTo_forProgramInfo() {
+ assertWithMessage("Identifier logically tuned to in DAB program info")
+ .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
+ }
+
+ @Test
+ public void getPhysicallyTunedTo_forProgramInfo() {
+ assertWithMessage("Identifier physically tuned to DAB program info")
+ .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
+ }
+
+ @Test
+ public void getRelatedContent_forProgramInfo() {
+ assertWithMessage("Related contents of DAB program info")
+ .that(DAB_PROGRAM_INFO.getRelatedContent())
+ .containsExactly(DAB_SID_EXT_IDENTIFIER_RELATED);
+ }
+
+ @Test
+ public void getChannel_forProgramInfo() {
+ assertWithMessage("Main channel of DAB program info")
+ .that(DAB_PROGRAM_INFO.getChannel()).isEqualTo(0);
+ }
+
+ @Test
+ public void getSubChannel_forProgramInfo() {
+ assertWithMessage("Sub channel of DAB program info")
+ .that(DAB_PROGRAM_INFO.getSubChannel()).isEqualTo(0);
+ }
+
+ @Test
+ public void isTuned_forProgramInfo() {
+ assertWithMessage("Tuned status of DAB program info")
+ .that(DAB_PROGRAM_INFO.isTuned()).isTrue();
+ }
+
+ @Test
+ public void isStereo_forProgramInfo() {
+ assertWithMessage("Stereo support in DAB program info")
+ .that(DAB_PROGRAM_INFO.isStereo()).isTrue();
+ }
+
+ @Test
+ public void isDigital_forProgramInfo() {
+ assertWithMessage("Digital DAB program info")
+ .that(DAB_PROGRAM_INFO.isDigital()).isTrue();
+ }
+
+ @Test
+ public void isLive_forProgramInfo() {
+ assertWithMessage("Live status of DAB program info")
+ .that(DAB_PROGRAM_INFO.isLive()).isTrue();
+ }
+
+ @Test
+ public void isMuted_forProgramInfo() {
+ assertWithMessage("Muted status of DAB program info")
+ .that(DAB_PROGRAM_INFO.isMuted()).isFalse();
+ }
+
+ @Test
+ public void isTrafficProgram_forProgramInfo() {
+ assertWithMessage("Traffic program support in DAB program info")
+ .that(DAB_PROGRAM_INFO.isTrafficProgram()).isFalse();
+ }
+
+ @Test
+ public void isTrafficAnnouncementActive_forProgramInfo() {
+ assertWithMessage("Active traffic announcement for DAB program info")
+ .that(DAB_PROGRAM_INFO.isTrafficAnnouncementActive()).isFalse();
+ }
+
+ @Test
+ public void getSignalStrength_forProgramInfo() {
+ assertWithMessage("Signal strength of DAB program info")
+ .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY);
+ }
+
+ @Test
+ public void getMetadata_forProgramInfo() {
+ assertWithMessage("Metadata of DAB program info")
+ .that(DAB_PROGRAM_INFO.getMetadata()).isEqualTo(METADATA);
+ }
+
+ @Test
+ public void getVendorInfo_forProgramInfo() {
+ assertWithMessage("Vendor info of DAB program info")
+ .that(DAB_PROGRAM_INFO.getVendorInfo()).isEmpty();
+ }
+
+ @Test
+ public void equals_withSameProgramInfo_returnsTrue() {
+ RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(DAB_SELECTOR);
+
+ assertWithMessage("The same program info")
+ .that(dabProgramInfoCompared).isEqualTo(DAB_PROGRAM_INFO);
+ }
+
+ @Test
+ public void equals_withSameProgramInfoOfDifferentSecondaryIdSelectors_returnsFalse() {
+ ProgramSelector dabSelectorCompared = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER,
+ new ProgramSelector.Identifier[]{DAB_FREQUENCY_IDENTIFIER},
+ /* vendorIds= */ null);
+ RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(dabSelectorCompared);
+
+ assertWithMessage("Program info with different secondary id selectors")
+ .that(DAB_PROGRAM_INFO).isNotEqualTo(dabProgramInfoCompared);
+ }
+
private static RadioManager.ModuleProperties createAmFmProperties() {
return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID,
IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES,
@@ -487,4 +633,16 @@ public final class RadioManagerTest {
private static RadioManager.AmBandConfig createAmBandConfig() {
return new RadioManager.AmBandConfig(createAmBandDescriptor());
}
+
+ private static RadioMetadata createMetadata() {
+ RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder();
+ return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest").build();
+ }
+
+ private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
+ return new RadioManager.ProgramInfo(selector, DAB_FREQUENCY_IDENTIFIER,
+ DAB_SID_EXT_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
+ SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
+ }
+
}
diff --git a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java
deleted file mode 100644
index 7c7cd12bcb73..000000000000
--- a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.time;
-
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class TimeConfigurationTest {
-
- @Test
- public void testBuilder() {
- TimeConfiguration first = new TimeConfiguration.Builder()
- .setAutoDetectionEnabled(true)
- .build();
-
- assertThat(first.isAutoDetectionEnabled()).isTrue();
-
- TimeConfiguration copyFromBuilderConfiguration = new TimeConfiguration.Builder(first)
- .build();
-
- assertThat(first).isEqualTo(copyFromBuilderConfiguration);
- }
-
- @Test
- public void testParcelable() {
- TimeConfiguration.Builder builder = new TimeConfiguration.Builder();
-
- assertRoundTripParcelable(builder.setAutoDetectionEnabled(true).build());
-
- assertRoundTripParcelable(builder.setAutoDetectionEnabled(false).build());
- }
-
-}
diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
index 3ab01f3d8832..e7d352cfee30 100644
--- a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -16,11 +16,9 @@
package android.app.time;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import android.os.ShellCommand;
@@ -31,35 +29,12 @@ import org.junit.runner.RunWith;
/**
* Tests for non-SDK methods on {@link UnixEpochTime}.
+ *
+ * <p>See also {@link android.app.time.cts.UnixEpochTimeTest} for SDK methods.
*/
@RunWith(AndroidJUnit4.class)
public class UnixEpochTimeTest {
- @Test
- public void testEqualsAndHashcode() {
- UnixEpochTime one1000one = new UnixEpochTime(1000, 1);
- assertEqualsAndHashCode(one1000one, one1000one);
-
- UnixEpochTime one1000two = new UnixEpochTime(1000, 1);
- assertEqualsAndHashCode(one1000one, one1000two);
-
- UnixEpochTime two1000 = new UnixEpochTime(1000, 2);
- assertNotEquals(one1000one, two1000);
-
- UnixEpochTime one2000 = new UnixEpochTime(2000, 1);
- assertNotEquals(one1000one, one2000);
- }
-
- private static void assertEqualsAndHashCode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
- @Test
- public void testParceling() {
- assertRoundTripParcelable(new UnixEpochTime(1000, 1));
- }
-
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noElapsedRealtime() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
@@ -91,22 +66,6 @@ public class UnixEpochTimeTest {
}
@Test
- public void testAt() {
- long timeMillis = 1000L;
- int elapsedRealtimeMillis = 100;
- UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis);
- // Reference time is after the timestamp.
- UnixEpochTime at125 = unixEpochTime.at(125);
- assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis());
- assertEquals(125, at125.getElapsedRealtimeMillis());
-
- // Reference time is before the timestamp.
- UnixEpochTime at75 = unixEpochTime.at(75);
- assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis());
- assertEquals(75, at75.getElapsedRealtimeMillis());
- }
-
- @Test
public void testElapsedRealtimeDifference() {
UnixEpochTime value1 = new UnixEpochTime(1000, 123L);
assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1));
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index c8de190b30b0..ab63f1475f83 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -17,6 +17,9 @@
package android.service.timezone;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -33,6 +36,32 @@ public class TimeZoneProviderEventTest {
@Test
public void isEquivalentToAndEquals() {
+ long creationElapsedMillis = 1111L;
+ TimeZoneProviderEvent failEvent =
+ TimeZoneProviderEvent.createPermanentFailureEvent(creationElapsedMillis, "one");
+ TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+
+ TimeZoneProviderEvent uncertainEvent =
+ TimeZoneProviderEvent.createUncertainEvent(creationElapsedMillis, providerStatus);
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(creationElapsedMillis)
+ .setTimeZoneIds(Collections.singletonList("Europe/London"))
+ .build();
+ TimeZoneProviderEvent suggestionEvent = TimeZoneProviderEvent.createSuggestionEvent(
+ creationElapsedMillis, suggestion, providerStatus);
+
+ assertNotEquals(failEvent, uncertainEvent);
+ assertNotEquivalentTo(failEvent, uncertainEvent);
+
+ assertNotEquals(failEvent, suggestionEvent);
+ assertNotEquivalentTo(failEvent, suggestionEvent);
+
+ assertNotEquals(uncertainEvent, suggestionEvent);
+ assertNotEquivalentTo(uncertainEvent, suggestionEvent);
+ }
+
+ @Test
+ public void isEquivalentToAndEquals_permanentFailure() {
TimeZoneProviderEvent fail1v1 =
TimeZoneProviderEvent.createPermanentFailureEvent(1111L, "one");
assertEquals(fail1v1, fail1v1);
@@ -51,44 +80,79 @@ public class TimeZoneProviderEventTest {
assertNotEquals(fail1v1, fail2);
assertIsEquivalentTo(fail1v1, fail2);
}
+ }
+
+ @Test
+ public void isEquivalentToAndEquals_uncertain() {
+ TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .build();
+ TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
- TimeZoneProviderEvent uncertain1v1 = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ TimeZoneProviderEvent uncertain1v1 =
+ TimeZoneProviderEvent.createUncertainEvent(1111L, status1);
assertEquals(uncertain1v1, uncertain1v1);
assertIsEquivalentTo(uncertain1v1, uncertain1v1);
assertNotEquals(uncertain1v1, null);
assertNotEquivalentTo(uncertain1v1, null);
{
- TimeZoneProviderEvent uncertain1v2 = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ TimeZoneProviderEvent uncertain1v2 =
+ TimeZoneProviderEvent.createUncertainEvent(1111L, status1);
assertEquals(uncertain1v1, uncertain1v2);
assertIsEquivalentTo(uncertain1v1, uncertain1v2);
- TimeZoneProviderEvent uncertain2 = TimeZoneProviderEvent.createUncertainEvent(2222L);
+ TimeZoneProviderEvent uncertain2 =
+ TimeZoneProviderEvent.createUncertainEvent(2222L, status1);
assertNotEquals(uncertain1v1, uncertain2);
assertIsEquivalentTo(uncertain1v1, uncertain2);
+
+ TimeZoneProviderEvent uncertain3 =
+ TimeZoneProviderEvent.createUncertainEvent(1111L, status2);
+ assertNotEquals(uncertain1v1, uncertain3);
+ assertNotEquivalentTo(uncertain1v1, uncertain3);
}
+ }
+ @Test
+ public void isEquivalentToAndEquals_suggestion() {
+ TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .build();
+ TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
.setElapsedRealtimeMillis(1111L)
.setTimeZoneIds(Collections.singletonList("Europe/London"))
.build();
TimeZoneProviderEvent certain1v1 =
- TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1);
+ TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1, status1);
assertEquals(certain1v1, certain1v1);
assertIsEquivalentTo(certain1v1, certain1v1);
assertNotEquals(certain1v1, null);
assertNotEquivalentTo(certain1v1, null);
{
- // Same suggestion, same time.
+ // Same time, suggestion, and status.
TimeZoneProviderEvent certain1v2 =
- TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1);
+ TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion1, status1);
assertEquals(certain1v1, certain1v2);
assertIsEquivalentTo(certain1v1, certain1v2);
- // Same suggestion, different time.
+ // Different time, same suggestion and status.
TimeZoneProviderEvent certain1v3 =
- TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1);
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1, status1);
assertNotEquals(certain1v1, certain1v3);
assertIsEquivalentTo(certain1v1, certain1v3);
@@ -100,7 +164,7 @@ public class TimeZoneProviderEventTest {
assertNotEquals(suggestion1, suggestion2);
TimeZoneProviderSuggestionTest.assertIsEquivalentTo(suggestion1, suggestion2);
TimeZoneProviderEvent certain2 =
- TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2);
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion2, status1);
assertNotEquals(certain1v1, certain2);
assertIsEquivalentTo(certain1v1, certain2);
@@ -109,16 +173,15 @@ public class TimeZoneProviderEventTest {
.setTimeZoneIds(Collections.singletonList("Europe/Paris"))
.build();
TimeZoneProviderEvent certain3 =
- TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3);
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion3, status1);
assertNotEquals(certain1v1, certain3);
assertNotEquivalentTo(certain1v1, certain3);
- }
- assertNotEquals(fail1v1, uncertain1v1);
- assertNotEquivalentTo(fail1v1, uncertain1v1);
-
- assertNotEquals(fail1v1, certain1v1);
- assertNotEquivalentTo(fail1v1, certain1v1);
+ TimeZoneProviderEvent certain4 =
+ TimeZoneProviderEvent.createSuggestionEvent(2222L, suggestion1, status2);
+ assertNotEquals(certain1v1, certain4);
+ assertNotEquivalentTo(certain1v1, certain4);
+ }
}
@Test
@@ -130,7 +193,8 @@ public class TimeZoneProviderEventTest {
@Test
public void testParcelable_uncertain() {
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L);
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+ 1111L, TimeZoneProviderStatus.UNKNOWN);
assertRoundTripParcelable(event);
}
@@ -139,8 +203,8 @@ public class TimeZoneProviderEventTest {
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
.setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
.build();
- TimeZoneProviderEvent event =
- TimeZoneProviderEvent.createSuggestionEvent(1111L, suggestion);
+ TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+ 1111L, suggestion, TimeZoneProviderStatus.UNKNOWN);
assertRoundTripParcelable(event);
}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
new file mode 100644
index 000000000000..d61c33c935db
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.timezone;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class TimeZoneProviderStatusTest {
+
+ @Test
+ public void testStatusValidation() {
+ TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(DEPENDENCY_STATUS_WORKING)
+ .build();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new TimeZoneProviderStatus.Builder(status)
+ .setLocationDetectionStatus(-1)
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> new TimeZoneProviderStatus.Builder(status)
+ .setConnectivityStatus(-1)
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> new TimeZoneProviderStatus.Builder(status)
+ .setTimeZoneResolutionStatus(-1)
+ .build());
+ }
+
+ @Test
+ public void testEqualsAndHashcode() {
+ TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .build();
+ assertEqualsAndHashcode(status1_1, status1_1);
+ assertNotEquals(status1_1, null);
+
+ {
+ TimeZoneProviderStatus status1_2 =
+ new TimeZoneProviderStatus.Builder(status1_1).build();
+ assertEqualsAndHashcode(status1_1, status1_2);
+ assertNotSame(status1_1, status1_2);
+ }
+
+ {
+ TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+ .build();
+ assertNotEquals(status1_1, status2);
+ }
+
+ {
+ TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
+ .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+ .build();
+ assertNotEquals(status1_1, status2);
+ }
+
+ {
+ TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
+ assertNotEquals(status1_1, status2);
+ }
+ }
+
+ private static void assertEqualsAndHashcode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ @Test
+ public void testParcelable() {
+ TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
+ assertRoundTripParcelable(status);
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3091e7..f370ebd94545 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -60,6 +60,8 @@ public class WindowOnBackInvokedDispatcherTest {
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
+ @Mock
+ private BackEvent mBackEvent;
@Before
public void setUp() throws Exception {
@@ -85,14 +87,14 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
Mockito.eq(mWindow),
captor.capture());
- captor.getAllValues().get(0).getCallback().onBackStarted();
+ captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted();
+ verify(mCallback1).onBackStarted(mBackEvent);
verifyZeroInteractions(mCallback2);
- captor.getAllValues().get(1).getCallback().onBackStarted();
+ captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted();
+ verify(mCallback2).onBackStarted(mBackEvent);
verifyNoMoreInteractions(mCallback1);
}
@@ -110,9 +112,9 @@ public class WindowOnBackInvokedDispatcherTest {
Mockito.eq(mWindow), captor.capture());
verifyNoMoreInteractions(mWindowSession);
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
- captor.getValue().getCallback().onBackStarted();
+ captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted();
+ verify(mCallback1).onBackStarted(mBackEvent);
}
@Test
@@ -148,8 +150,8 @@ public class WindowOnBackInvokedDispatcherTest {
mDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
- captor.getValue().getCallback().onBackStarted();
+ captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted();
+ verify(mCallback2).onBackStarted(mBackEvent);
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 699e7947410e..decfb9fc59df 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -265,6 +265,7 @@ applications that come with the platform
<permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
<permission name="android.permission.INSTALL_PACKAGES"/>
<permission name="android.permission.INSTALL_PACKAGE_UPDATES"/>
+ <permission name="android.permission.KILL_ALL_BACKGROUND_PROCESSES"/>
<!-- Needed for test only -->
<permission name="android.permission.ACCESS_MTP"/>
<!-- Needed for test only -->
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 1c41d06a3da2..9940ca3933c8 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -47,7 +47,7 @@ public final class BLASTBufferQueue {
TransactionHangCallback callback);
public interface TransactionHangCallback {
- void onTransactionHang(boolean isGpuHang);
+ void onTransactionHang(String reason);
}
/** Create a new connection with the surface flinger. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 43f39b78ca1a..db5de431cced 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -76,10 +76,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
"persist.wm.debug.predictive_back_progress_threshold";
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
- SETTING_VALUE_ON) != SETTING_VALUE_OFF;
+ SETTING_VALUE_ON) != SETTING_VALUE_ON;
private static final int PROGRESS_THRESHOLD = SystemProperties
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
-
+ /** Flag for U animation features */
+ public static boolean IS_U_ANIMATION_ENABLED =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
+ SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+ /** Predictive back animation developer option */
+ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
// TODO (b/241808055) Find a appropriate time to remove during refactor
private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
/**
@@ -88,8 +93,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
private static final long MAX_TRANSITION_DURATION = 2000;
- private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
-
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -143,53 +146,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
};
- /**
- * Helper class to record the touch location for gesture start and latest.
- */
- private static class TouchTracker {
- /**
- * Location of the latest touch event
- */
- private float mLatestTouchX;
- private float mLatestTouchY;
- private int mSwipeEdge;
- private float mProgressThreshold;
-
- /**
- * Location of the initial touch event of the back gesture.
- */
- private float mInitTouchX;
- private float mInitTouchY;
-
- void update(float touchX, float touchY, int swipeEdge) {
- mLatestTouchX = touchX;
- mLatestTouchY = touchY;
- mSwipeEdge = swipeEdge;
- }
-
- void setGestureStartLocation(float touchX, float touchY) {
- mInitTouchX = touchX;
- mInitTouchY = touchY;
- }
-
- void setProgressThreshold(float progressThreshold) {
- mProgressThreshold = progressThreshold;
- }
-
- float getProgress(float touchX) {
- int deltaX = Math.round(touchX - mInitTouchX);
- float progressThreshold = PROGRESS_THRESHOLD >= 0
- ? PROGRESS_THRESHOLD : mProgressThreshold;
- return Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
- }
-
- void reset() {
- mInitTouchX = 0;
- mInitTouchY = 0;
- mSwipeEdge = -1;
- }
- }
-
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -221,6 +177,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTransitions = transitions;
}
+ @VisibleForTesting
+ void setEnableUAnimation(boolean enable) {
+ IS_U_ANIMATION_ENABLED = enable;
+ }
+
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
createAdapter();
@@ -374,7 +335,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mTransitionInProgress) {
return;
}
- mTouchTracker.update(touchX, touchY, swipeEdge);
+
+ mTouchTracker.update(touchX, touchY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -384,7 +346,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
- onGestureStarted(touchX, touchY);
+ onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
onMove(touchX, touchY, swipeEdge);
@@ -398,14 +360,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private void onGestureStarted(float touchX, float touchY) {
+ private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
finishBackNavigation();
}
- mTouchTracker.setGestureStartLocation(touchX, touchY);
+ mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
mBackGestureStarted = true;
try {
@@ -428,12 +390,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final IOnBackInvokedCallback targetCallback;
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
if (shouldDispatchToAnimator) {
- targetCallback = mAnimationDefinition.get(backType).getGestureStartedCallback();
+ mAnimationDefinition.get(backType).startGesture();
} else {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- }
- if (shouldDispatchToAnimator) {
- dispatchOnBackStarted(targetCallback);
+ dispatchOnBackStarted(targetCallback, mTouchTracker.createStartEvent(null));
}
}
@@ -441,12 +401,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
return;
}
- mTouchTracker.update(touchX, touchY, swipeEdge);
- float progress = mTouchTracker.getProgress(touchX);
- int backType = mBackNavigationInfo.getType();
+ final BackEvent backEvent = mTouchTracker.createProgressEvent();
- BackEvent backEvent = new BackEvent(touchX, touchY, progress, swipeEdge);
- IOnBackInvokedCallback targetCallback = null;
+ int backType = mBackNavigationInfo.getType();
+ IOnBackInvokedCallback targetCallback;
if (shouldDispatchToAnimator(backType)) {
targetCallback = mAnimationDefinition.get(backType).getCallback();
} else {
@@ -532,18 +490,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mAnimationDefinition.contains(backType);
}
- private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
+ BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackStarted();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackStarted(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
}
- private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
@@ -554,29 +515,39 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
- callback.onBackCancelled();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackCancelled();
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
}
- private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
+ private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackProgressed(backEvent);
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackProgressed(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
+ private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+ return (IS_U_ANIMATION_ENABLED || callback == mAnimationDefinition.get(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME).getCallback())
+ && mEnableAnimations.get();
+ }
+
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -585,6 +556,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
mTriggerBack = triggerBack;
+ mTouchTracker.setTriggerBack(triggerBack);
}
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
@@ -670,13 +642,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
runner.startAnimation(apps, wallpapers, nonApps,
BackAnimationController.this::onBackAnimationFinished);
+ if (apps.length >= 1) {
+ final int backType = mBackNavigationInfo.getType();
+ IOnBackInvokedCallback targetCallback = mAnimationDefinition.get(backType)
+ .getCallback();
+ dispatchOnBackStarted(
+ targetCallback, mTouchTracker.createStartEvent(apps[0]));
+ }
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackEvent backFinish = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
- mTouchTracker.mSwipeEdge);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
startTransition();
runner.consumeIfGestureFinished(backFinish);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 12bbf73af561..c53fcfc99c9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -79,9 +79,8 @@ class BackAnimationRunner {
}
}
- IOnBackInvokedCallback getGestureStartedCallback() {
+ void startGesture() {
mWaitingAnimation = true;
- return mCallback;
}
boolean onGestureFinished(boolean triggerBack) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
new file mode 100644
index 000000000000..ccfac65d6342
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.os.SystemProperties;
+import android.view.RemoteAnimationTarget;
+import android.window.BackEvent;
+
+/**
+ * Helper class to record the touch location for gesture and generate back events.
+ */
+class TouchTracker {
+ private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
+ "persist.wm.debug.predictive_back_progress_threshold";
+ private static final int PROGRESS_THRESHOLD = SystemProperties
+ .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ private float mProgressThreshold;
+ /**
+ * Location of the latest touch event
+ */
+ private float mLatestTouchX;
+ private float mLatestTouchY;
+ private boolean mTriggerBack;
+
+ /**
+ * Location of the initial touch event of the back gesture.
+ */
+ private float mInitTouchX;
+ private float mInitTouchY;
+ private float mStartThresholdX;
+ private int mSwipeEdge;
+ private boolean mCancelled;
+
+ void update(float touchX, float touchY) {
+ /**
+ * If back was previously cancelled but the user has started swiping in the forward
+ * direction again, restart back.
+ */
+ if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
+ || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
+ mCancelled = false;
+ mStartThresholdX = touchX;
+ }
+ mLatestTouchX = touchX;
+ mLatestTouchY = touchY;
+ }
+
+ void setTriggerBack(boolean triggerBack) {
+ if (mTriggerBack != triggerBack && !triggerBack) {
+ mCancelled = true;
+ }
+ mTriggerBack = triggerBack;
+ }
+
+ void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
+ mInitTouchX = touchX;
+ mInitTouchY = touchY;
+ mSwipeEdge = swipeEdge;
+ mStartThresholdX = mInitTouchX;
+ }
+
+ void reset() {
+ mInitTouchX = 0;
+ mInitTouchY = 0;
+ mStartThresholdX = 0;
+ mCancelled = false;
+ mTriggerBack = false;
+ mSwipeEdge = BackEvent.EDGE_LEFT;
+ }
+
+ BackEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ }
+
+ BackEvent createProgressEvent() {
+ float progressThreshold = PROGRESS_THRESHOLD >= 0
+ ? PROGRESS_THRESHOLD : mProgressThreshold;
+ progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
+ float progress = 0;
+ // Progress is always 0 when back is cancelled and not restarted.
+ if (!mCancelled) {
+ // If back is committed, progress is the distance between the last and first touch
+ // point, divided by the max drag distance. Otherwise, it's the distance between
+ // the last touch point and the starting threshold, divided by max drag distance.
+ // The starting threshold is initially the first touch location, and updated to
+ // the location everytime back is restarted after being cancelled.
+ float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
+ float deltaX = Math.max(
+ mSwipeEdge == BackEvent.EDGE_LEFT
+ ? mLatestTouchX - startX
+ : startX - mLatestTouchX,
+ 0);
+ progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
+ }
+ return createProgressEvent(progress);
+ }
+
+ BackEvent createProgressEvent(float progress) {
+ return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ }
+
+ public void setProgressThreshold(float progressThreshold) {
+ mProgressThreshold = progressThreshold;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
index d6803e8052c6..d3a9a672ec76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -52,7 +52,7 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
}
Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1, BITMAP_GENERATION_MODE_WITH_SHADOW);
+ userBadgedAppIcon, 1, MODE_WITH_SHADOW);
return createIconBitmap(userBadgedBitmap);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 5dab8a071f76..4ded3ea951e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -79,6 +79,6 @@ public class BubbleIconFactory extends BaseIconFactory {
true /* shrinkNonAdaptiveIcons */,
null /* outscale */,
outScale);
- return createIconBitmap(icon, outScale[0], BITMAP_GENERATION_MODE_WITH_SHADOW);
+ return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index afb64c9eec41..43d3f36f1fe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -60,7 +60,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
FloatingContentCoordinator.FloatingContent {
public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
- SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true);
+ SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false);
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 8cee4f1dc8fb..6ce981e25f5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -432,7 +432,8 @@ public class SplashscreenContentDrawer {
final ShapeIconFactory factory = new ShapeIconFactory(
SplashscreenContentDrawer.this.mContext,
scaledIconDpi, mFinalIconSize);
- final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
+ final Bitmap bitmap = factory.createScaledBitmap(iconDrawable,
+ BaseIconFactory.MODE_DEFAULT);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
createIconDrawable(new BitmapDrawable(bitmap), true,
mHighResIconProvider.mLoadInDetail);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 2b27baeb515a..66d0a2aa409b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.transition;
-import static android.hardware.HardwareBuffer.RGBA_8888;
-import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
import static android.util.RotationUtils.deltaRotation;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
@@ -37,8 +35,6 @@ import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.media.Image;
-import android.media.ImageReader;
import android.util.Slog;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -50,12 +46,11 @@ import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* This class handles the rotation animation when the device is rotated.
@@ -173,7 +168,7 @@ class ScreenRotationAnimation {
t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
- mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
+ mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
}
}
@@ -404,93 +399,6 @@ class ScreenRotationAnimation {
mTransactionPool.release(t);
}
- /**
- * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
- * luminance at the borders of the bitmap
- * @return the average luminance of all the pixels at the borders of the bitmap
- */
- private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
- // Cannot read content from buffer with protected usage.
- if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
- || hasProtectedContent(hardwareBuffer)) {
- return 0;
- }
-
- ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
- hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
- ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
- Image image = ir.acquireLatestImage();
- if (image == null || image.getPlanes().length == 0) {
- return 0;
- }
-
- Image.Plane plane = image.getPlanes()[0];
- ByteBuffer buffer = plane.getBuffer();
- int width = image.getWidth();
- int height = image.getHeight();
- int pixelStride = plane.getPixelStride();
- int rowStride = plane.getRowStride();
- float[] borderLumas = new float[2 * width + 2 * height];
-
- // Grab the top and bottom borders
- int l = 0;
- for (int x = 0; x < width; x++) {
- borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
- }
-
- // Grab the left and right borders
- for (int y = 0; y < height; y++) {
- borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
- }
-
- // Cleanup
- ir.close();
-
- // Oh, is this too simple and inefficient for you?
- // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
- Arrays.sort(borderLumas);
- return borderLumas[borderLumas.length / 2];
- }
-
- /**
- * @return whether the hardwareBuffer passed in is marked as protected.
- */
- private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
- return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
- }
-
- private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
- int pixelStride, int rowStride) {
- int offset = y * rowStride + x * pixelStride;
- int pixel = 0;
- pixel |= (buffer.get(offset) & 0xff) << 16; // R
- pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
- pixel |= (buffer.get(offset + 2) & 0xff); // B
- pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
- return Color.valueOf(pixel).luminance();
- }
-
- /**
- * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
- * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
- */
- private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) {
- if (surfaceControl == null) {
- return 0;
- }
-
- Rect crop = new Rect(0, 0, bounds.width(), bounds.height());
- ScreenCapture.ScreenshotHardwareBuffer buffer =
- ScreenCapture.captureLayers(surfaceControl, crop, 1);
- if (buffer == null) {
- return 0;
- }
-
- return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
- }
-
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index e90389764af3..f209521b1da4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -33,6 +33,8 @@ public class SplitBounds implements Parcelable {
// This class is orientation-agnostic, so we compute both for later use
public final float topTaskPercent;
public final float leftTaskPercent;
+ public final float dividerWidthPercent;
+ public final float dividerHeightPercent;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -62,8 +64,12 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = false;
}
- leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
- topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+ float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+ float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+ leftTaskPercent = leftTopBounds.width() / totalWidth;
+ topTaskPercent = leftTopBounds.height() / totalHeight;
+ dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+ dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
public SplitBounds(Parcel parcel) {
@@ -75,6 +81,8 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = parcel.readBoolean();
leftTopTaskId = parcel.readInt();
rightBottomTaskId = parcel.readInt();
+ dividerWidthPercent = parcel.readInt();
+ dividerHeightPercent = parcel.readInt();
}
@Override
@@ -87,6 +95,8 @@ public class SplitBounds implements Parcelable {
parcel.writeBoolean(appsStackedVertically);
parcel.writeInt(leftTopTaskId);
parcel.writeInt(rightBottomTaskId);
+ parcel.writeFloat(dividerWidthPercent);
+ parcel.writeFloat(dividerHeightPercent);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 7d1f130daaef..9d61c14e1435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -101,9 +101,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
- final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -114,6 +114,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
int outsetRightId = R.dimen.freeform_resize_handle;
int outsetBottomId = R.dimen.freeform_resize_handle;
+ mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 01cab9aae312..b314163802ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -91,7 +91,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mTaskBackgroundSurface;
SurfaceControl mCaptionContainerSurface;
- private CaptionWindowManager mCaptionWindowManager;
+ private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private final Rect mCaptionInsetsRect = new Rect();
@@ -199,13 +199,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- final int decorContainerOffsetX = -loadResource(params.mOutsetLeftId);
- final int decorContainerOffsetY = -loadResource(params.mOutsetTopId);
+ final Resources resources = mDecorWindowContext.getResources();
+ final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
- + loadResource(params.mOutsetRightId)
+ + loadDimensionPixelSize(resources, params.mOutsetRightId)
- decorContainerOffsetX;
outResult.mHeight = taskBounds.height()
- + loadResource(params.mOutsetBottomId)
+ + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- decorContainerOffsetY;
startT.setPosition(
mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
@@ -225,7 +226,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- float shadowRadius = loadResource(params.mShadowRadiusId);
+ float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
@@ -248,8 +249,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- final int captionHeight = loadResource(params.mCaptionHeightId);
- final int captionWidth = loadResource(params.mCaptionWidthId);
+ final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
//Prevent caption from going offscreen if task is too high up
final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
@@ -264,8 +265,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+ mCaptionWindowManager = new WindowlessWindowManager(
+ mTaskInfo.getConfiguration(), mCaptionContainerSurface,
+ null /* hostInputToken */);
}
// Caption view
@@ -309,13 +311,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setCrop(mTaskSurface, mTaskSurfaceCrop);
}
- private int loadResource(int resourceId) {
- if (resourceId == Resources.ID_NULL) {
- return 0;
- }
- return mDecorWindowContext.getResources().getDimensionPixelSize(resourceId);
- }
-
/**
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -374,33 +369,18 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
- static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
- int mWidth;
- int mHeight;
- T mRootView;
-
- void reset() {
- mWidth = 0;
- mHeight = 0;
- mRootView = null;
- }
- }
-
- private static class CaptionWindowManager extends WindowlessWindowManager {
- CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
- super(config, rootSurface, null /* hostInputToken */);
- }
-
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
+ private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
}
+ return resources.getDimensionPixelSize(resourceId);
}
- interface SurfaceControlViewHostFactory {
- default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
- return new SurfaceControlViewHost(c, d, wmm);
+ private static float loadDimension(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
}
+ return resources.getDimension(resourceId);
}
static class RelayoutParams{
@@ -433,6 +413,23 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mOutsetLeftId = Resources.ID_NULL;
mOutsetRightId = Resources.ID_NULL;
}
+ }
+ static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mWidth;
+ int mHeight;
+ T mRootView;
+
+ void reset() {
+ mWidth = 0;
+ mHeight = 0;
+ mRootView = null;
+ }
+ }
+
+ interface SurfaceControlViewHostFactory {
+ default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+ return new SurfaceControlViewHost(c, d, wmm);
+ }
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index fa783f231607..45eae2e2fe40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -26,7 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -35,7 +34,6 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -95,7 +93,9 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- private fun secondaryAppBoundsIsFullscreenAtEnd_internal() {
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsFullscreenAtEnd() {
testSpec.assertLayers {
this.isVisible(secondaryApp)
.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -117,20 +117,6 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
@Presubmit
@Test
- fun secondaryAppBoundsIsFullscreenAtEnd() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- secondaryAppBoundsIsFullscreenAtEnd_internal()
- }
-
- @FlakyTest(bugId = 250528485)
- @Test
- fun secondaryAppBoundsIsFullscreenAtEnd_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- secondaryAppBoundsIsFullscreenAtEnd_internal()
- }
-
- @Presubmit
- @Test
fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 84a8c0a59f32..73159c981b82 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
@@ -146,19 +145,15 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
// robust enough to get the correct end state.
}
- @FlakyTest(bugId = 241524174)
@Test
fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @FlakyTest(bugId = 241524174)
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @FlakyTest(bugId = 241524174)
@Test
fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
- @FlakyTest(bugId = 241524174)
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
@@ -166,9 +161,6 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
portraitPosTop = true
)
- // TODO(b/246490534): Move back to presubmit after withAppTransitionIdle is robust enough to
- // get the correct end state.
- @FlakyTest(bugId = 246490534)
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
@@ -176,11 +168,9 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
portraitPosTop = false
)
- @FlakyTest(bugId = 241524174)
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @FlakyTest(bugId = 241524174)
@Test
fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
new file mode 100644
index 000000000000..8949a75d1a15
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <!-- Resources used in WindowDecorationTests -->
+ <dimen name="test_freeform_decor_caption_height">32dp</dimen>
+ <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+ <dimen name="test_window_decor_left_outset">10dp</dimen>
+ <dimen name="test_window_decor_top_outset">20dp</dimen>
+ <dimen name="test_window_decor_right_outset">30dp</dimen>
+ <dimen name="test_window_decor_bottom_outset">40dp</dimen>
+ <dimen name="test_window_decor_shadow_radius">5dp</dimen>
+ <dimen name="test_window_decor_resize_handle">10dp</dimen>
+</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6484b0759bd7..7896247c5f5a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -128,6 +128,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
mContentResolver, mTransitions);
+ mController.setEnableUAnimation(true);
mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
@@ -206,10 +207,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -236,11 +236,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
- verify(appCallback, never()).onBackStarted();
+ verify(appCallback, never()).onBackStarted(any(BackEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted();
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
verify(mBackAnimationRunner, never()).onAnimationStart(
@@ -279,7 +279,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@@ -301,9 +301,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
-
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
}
@@ -318,7 +317,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
// Check that back invocation is dispatched.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
new file mode 100644
index 000000000000..3aefc3f03a8a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static org.junit.Assert.assertEquals;
+
+import android.window.BackEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TouchTrackerTest {
+ private static final float FAKE_THRESHOLD = 400;
+ private static final float INITIAL_X_LEFT_EDGE = 5;
+ private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
+ private TouchTracker mTouchTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ mTouchTracker = new TouchTracker();
+ mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
+ }
+
+ @Test
+ public void generatesProgress_onStart() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ BackEvent event = mTouchTracker.createStartEvent(null);
+ assertEquals(event.getProgress(), 0f, 0f);
+ }
+
+ @Test
+ public void generatesProgress_leftEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ float touchX = 10;
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX += 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX += 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+ }
+
+ @Test
+ public void generatesProgress_rightEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
+ float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX -= 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX += 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+ }
+
+ private float getProgress() {
+ return mTouchTracker.createProgressEvent().getProgress();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 103c8dab17d5..4d37e5dbc4dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -50,12 +50,13 @@ import android.view.WindowManager.LayoutParams;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.tests.R;
import org.junit.Before;
import org.junit.Test;
@@ -145,8 +146,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle,
- R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -196,13 +200,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-// int outsetLeftId = R.dimen.split_divider_bar_width;
-// int outsetTopId = R.dimen.gestures_onehanded_drag_threshold;
-// int outsetRightId = R.dimen.freeform_resize_handle;
-// int outsetBottomId = R.dimen.bubble_dismiss_target_padding_x;
-// mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle,
- R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -211,8 +213,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -60, -60);
- verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 420, 220);
+ verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -225,36 +227,34 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -6, -156);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 432);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
- argThat(lp -> lp.height == 432
+ argThat(lp -> lp.height == 64
&& lp.width == 432
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 516),
+ new Rect(100, 300, 400, 332),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setCrop(taskSurface, new Rect(-60, -60, 360, 160));
+ .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
verify(mMockSurfaceControlStartT)
.show(taskSurface);
- assertEquals(420, mRelayoutResult.mWidth);
+ assertEquals(380, mRelayoutResult.mWidth);
assertEquals(220, mRelayoutResult.mHeight);
-
-
}
@Test
@@ -293,8 +293,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle,
- R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -365,7 +368,8 @@ public class WindowDecorationTests extends ShellTestCase {
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
- return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
+ return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
+ mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, testSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
@@ -417,12 +421,10 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
-
mRelayoutParams.mLayoutResId = 0;
- mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_width;
- mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
- mRelayoutParams.mShadowRadiusId =
- R.dimen.freeform_decor_shadow_unfocused_thickness;
+ mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+ mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 2547a963eb31..d975e96f193b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6632,8 +6632,8 @@ public class AudioManager {
}
}
if (k == ports.size()) {
- // this hould never happen
- Log.e(TAG, "updatePortConfig port not found for handle: "+port.handle().id());
+ // This can happen in case of stale audio patch referring to a removed device and is
+ // handled by the caller.
return null;
}
AudioGainConfig gainCfg = portCfg.gain();
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 39b3d0b47a27..0291f64c0640 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -264,10 +264,9 @@ public class ImageWriter implements AutoCloseable {
if (useSurfaceImageFormatInfo) {
// nativeInit internally overrides UNKNOWN format. So does surface format query after
// nativeInit and before getEstimatedNativeAllocBytes().
- imageFormat = SurfaceUtils.getSurfaceFormat(surface);
- mDataSpace = dataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
- mHardwareBufferFormat =
- hardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
+ mHardwareBufferFormat = hardwareBufferFormat = SurfaceUtils.getSurfaceFormat(surface);
+ mDataSpace = dataSpace = SurfaceUtils.getSurfaceDataspace(surface);
+ imageFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
}
// Estimate the native buffer allocation size and register it so it gets accounted for
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 74c549943a74..ee825881ecae 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -240,8 +240,7 @@ public final class MidiManager {
* @param handler The {@link android.os.Handler Handler} that will be used for delivering the
* device notifications. If handler is null, then the thread used for the
* callback is unspecified.
- * @deprecated Use the {@link #registerDeviceCallback}
- * method with Executor and transport instead.
+ * @deprecated Use {@link #registerDeviceCallback(int, Executor, DeviceCallback)} instead.
*/
@Deprecated
public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 1e56a9340294..6990ad0fbd7d 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -10,7 +10,7 @@ package {
android_app {
name: "CarrierDefaultApp",
srcs: ["src/**/*.java"],
- static_libs: ["SliceStore"],
+ libs: ["SliceStore"],
platform_apis: true,
certificate: "platform",
}
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 9566f22a7274..a5b104b597ee 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<application
android:label="@string/app_name"
diff --git a/packages/CompanionDeviceManager/TEST_MAPPING b/packages/CompanionDeviceManager/TEST_MAPPING
deleted file mode 100644
index 63f54fa35158..000000000000
--- a/packages/CompanionDeviceManager/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsOsTestCases",
- "options": [
- {
- "include-filter": "android.os.cts.CompanionDeviceManagerTest"
- }
- ]
- }
- ]
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index ec0c5b708abe..64a12cd12f35 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,22 +16,24 @@
package com.android.credentialmanager
-import android.app.Activity
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
import android.content.Intent
+import android.credentials.CreateCredentialRequest
import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
-import android.credentials.ui.UserSelectionResult
+import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.UserSelectionDialogResult
import android.graphics.drawable.Icon
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -49,11 +51,7 @@ class CredentialManagerRepo(
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: RequestInfo(
- Binder(),
- RequestInfo.TYPE_CREATE,
- /*isFirstUsage=*/false
- )
+ ) ?: testRequestInfo()
providerList = intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_PROVIDER_DATA_LIST,
@@ -67,21 +65,20 @@ class CredentialManagerRepo(
}
fun onCancel() {
- resultReceiver?.send(Activity.RESULT_CANCELED, null)
+ val resultData = Bundle()
+ BaseDialogResult.addToBundle(BaseDialogResult(requestInfo.token), resultData)
+ resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
}
fun onOptionSelected(providerPackageName: String, entryId: Int) {
- val userSelectionResult = UserSelectionResult(
+ val userSelectionDialogResult = UserSelectionDialogResult(
requestInfo.token,
providerPackageName,
entryId
)
val resultData = Bundle()
- resultData.putParcelable(
- UserSelectionResult.EXTRA_USER_SELECTION_RESULT,
- userSelectionResult
- )
- resultReceiver?.send(Activity.RESULT_OK, resultData)
+ UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
+ resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, resultData)
}
fun getCredentialInitialUiState(): GetCredentialUiState {
@@ -95,9 +92,12 @@ class CredentialManagerRepo(
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
val providerList = CreateFlowUtils.toProviderList(providerList, context)
+ val requestDisplayInfo = RequestDisplayInfo(
+ "Elisa Beckett", "beckett-bakert@gmail.com", "TYPE_CREATE")
return CreatePasskeyUiState(
providers = providerList,
currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ requestDisplayInfo,
)
}
@@ -175,4 +175,18 @@ class CredentialManagerRepo(
slice
)
}
+
+ private fun testRequestInfo(): RequestInfo {
+ val data = Bundle()
+ return RequestInfo.newCreateRequestInfo(
+ Binder(),
+ CreateCredentialRequest(
+ // TODO: use the jetpack type and utils once defined.
+ "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
+ data
+ ),
+ /*isFirstUsage=*/false,
+ "tribank.us"
+ )
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 19820d6cd98c..e291cc206691 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -34,6 +34,21 @@ data class CreateOptionInfo(
val usageData: String
)
+data class RequestDisplayInfo(
+ val userName: String,
+ val displayName: String,
+ val type: String,
+)
+
+/**
+ * This is initialized to be the most recent used. Can then be changed if
+ * user selects a different entry on the more option page.
+ */
+data class ActiveEntry (
+ val activeProvider: ProviderInfo,
+ val activeCreateOptionInfo: CreateOptionInfo,
+)
+
/** The name of the current screen. */
enum class CreateScreenState {
PASSKEY_INTRO,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 82fce9f7a98d..fbbc3ac20e33 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -70,21 +70,21 @@ fun CreatePasskeyScreen(
onProviderSelected = {viewModel.onProviderSelected(it)}
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- providerInfo = uiState.selectedProvider!!,
- onOptionSelected = {viewModel.onCreateOptionSelected(it)},
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ onOptionSelected = {viewModel.onPrimaryCreateOptionInfoSelected()},
onCancel = {viewModel.onCancel()},
multiProvider = uiState.providers.size > 1,
- onMoreOptionsSelected = {viewModel.onMoreOptionsSelected(it)}
+ onMoreOptionsSelected = {viewModel.onMoreOptionsSelected()}
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- providerInfo = uiState.selectedProvider!!,
providerList = uiState.providers,
- onBackButtonSelected = {viewModel.onBackButtonSelected(it)},
+ onBackButtonSelected = {viewModel.onBackButtonSelected()},
onOptionSelected = {viewModel.onMoreOptionsRowSelected(it)}
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
- providerInfo = uiState.selectedProvider!!,
- onDefaultOrNotSelected = {viewModel.onDefaultOrNotSelected(it)}
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ onDefaultOrNotSelected = {viewModel.onDefaultOrNotSelected()}
)
}
},
@@ -218,10 +218,9 @@ fun ProviderSelectionCard(
@ExperimentalMaterialApi
@Composable
fun MoreOptionsSelectionCard(
- providerInfo: ProviderInfo,
providerList: List<ProviderInfo>,
- onBackButtonSelected: (String) -> Unit,
- onOptionSelected: (String) -> Unit
+ onBackButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit
) {
Card(
backgroundColor = lightBackgroundColor,
@@ -235,7 +234,7 @@ fun MoreOptionsSelectionCard(
elevation = 0.dp,
navigationIcon =
{
- IconButton(onClick = { onBackButtonSelected(providerInfo.name) }) {
+ IconButton(onClick = onBackButtonSelected) {
Icon(Icons.Filled.ArrowBack, "backIcon"
)
}
@@ -264,9 +263,12 @@ fun MoreOptionsSelectionCard(
providerList.forEach { providerInfo ->
providerInfo.createOptions.forEach { createOptionInfo ->
item {
- MoreOptionsInfoRow(providerInfo = providerInfo,
+ MoreOptionsInfoRow(
+ providerInfo = providerInfo,
createOptionInfo = createOptionInfo,
- onOptionSelected = onOptionSelected)
+ onOptionSelected = {
+ onOptionSelected(ActiveEntry(providerInfo, createOptionInfo))
+ })
}
}
}
@@ -285,7 +287,7 @@ fun MoreOptionsSelectionCard(
@Composable
fun MoreOptionsRowIntroCard(
providerInfo: ProviderInfo,
- onDefaultOrNotSelected: (String) -> Unit,
+ onDefaultOrNotSelected: () -> Unit,
) {
Card(
backgroundColor = lightBackgroundColor,
@@ -302,11 +304,11 @@ fun MoreOptionsRowIntroCard(
) {
CancelButton(
stringResource(R.string.use_once),
- onclick = { onDefaultOrNotSelected(providerInfo.name) }
+ onclick = onDefaultOrNotSelected
)
ConfirmButton(
stringResource(R.string.set_as_default),
- onclick = { onDefaultOrNotSelected(providerInfo.name) }
+ onclick = onDefaultOrNotSelected
)
}
Divider(
@@ -388,11 +390,12 @@ fun NavigationButton(
@ExperimentalMaterialApi
@Composable
fun CreationSelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
providerInfo: ProviderInfo,
- onOptionSelected: (Int) -> Unit,
+ onOptionSelected: () -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
- onMoreOptionsSelected: (String) -> Unit,
+ onMoreOptionsSelected: () -> Unit,
) {
Card(
backgroundColor = lightBackgroundColor,
@@ -427,14 +430,13 @@ fun CreationSelectionCard(
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- providerInfo.createOptions.forEach {
item {
- CreateOptionRow(createOptionInfo = it, onOptionSelected = onOptionSelected)
+ PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
+ onOptionSelected = onOptionSelected)
}
- }
if (multiProvider) {
item {
- MoreOptionsRow(onSelect = { onMoreOptionsSelected(providerInfo.name) })
+ MoreOptionsRow(onSelect = onMoreOptionsSelected)
}
}
}
@@ -494,14 +496,45 @@ fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (Int)
@ExperimentalMaterialApi
@Composable
+fun PrimaryCreateOptionRow(
+ requestDisplayInfo: RequestDisplayInfo,
+ onOptionSelected: () -> Unit
+) {
+ Chip(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {onOptionSelected()},
+ // TODO: Add an icon generated by provider according to requestDisplayInfo type
+ colors = ChipDefaults.chipColors(
+ backgroundColor = Grey100,
+ leadingIconContentColor = Grey100
+ ),
+ shape = Shapes.large
+ ) {
+ Column() {
+ Text(
+ text = requestDisplayInfo.userName,
+ style = Typography.h6,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = requestDisplayInfo.displayName,
+ style = Typography.body2,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@ExperimentalMaterialApi
+@Composable
fun MoreOptionsInfoRow(
providerInfo: ProviderInfo,
createOptionInfo: CreateOptionInfo,
- onOptionSelected: (String) -> Unit
+ onOptionSelected: () -> Unit
) {
Chip(
modifier = Modifier.fillMaxWidth(),
- onClick = { onOptionSelected(providerInfo.name) },
+ onClick = onOptionSelected,
leadingIcon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index ff44e2ee1b06..38486e2c7d74 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -30,7 +30,8 @@ import com.android.credentialmanager.common.ResultState
data class CreatePasskeyUiState(
val providers: List<ProviderInfo>,
val currentScreenState: CreateScreenState,
- val selectedProvider: ProviderInfo? = null,
+ val requestDisplayInfo: RequestDisplayInfo,
+ val activeEntry: ActiveEntry? = null,
)
class CreatePasskeyViewModel(
@@ -56,7 +57,8 @@ class CreatePasskeyViewModel(
} else if (uiState.providers.size == 1){
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- selectedProvider = uiState.providers.first()
+ activeEntry = ActiveEntry(uiState.providers.first(),
+ uiState.providers.first().createOptions.first())
)
} else {
throw java.lang.IllegalStateException("Empty provider list.")
@@ -66,14 +68,15 @@ class CreatePasskeyViewModel(
fun onProviderSelected(providerName: String) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- selectedProvider = getProviderInfoByName(providerName)
+ activeEntry = ActiveEntry(getProviderInfoByName(providerName),
+ getProviderInfoByName(providerName).createOptions.first())
)
}
fun onCreateOptionSelected(createOptionId: Int) {
Log.d("Account Selector", "Option selected for creation: $createOptionId")
CredentialManagerRepo.getInstance().onOptionSelected(
- uiState.selectedProvider!!.name,
+ uiState.activeEntry?.activeProvider!!.name,
createOptionId
)
dialogResult.value = DialogResult(
@@ -87,24 +90,22 @@ class CreatePasskeyViewModel(
}
}
- fun onMoreOptionsSelected(providerName: String) {
+ fun onMoreOptionsSelected() {
uiState = uiState.copy(
- currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
- selectedProvider = getProviderInfoByName(providerName)
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
)
}
- fun onBackButtonSelected(providerName: String) {
+ fun onBackButtonSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- selectedProvider = getProviderInfoByName(providerName)
)
}
- fun onMoreOptionsRowSelected(providerName: String) {
+ fun onMoreOptionsRowSelected(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
- selectedProvider = getProviderInfoByName(providerName)
+ activeEntry = activeEntry
)
}
@@ -113,11 +114,24 @@ class CreatePasskeyViewModel(
dialogResult.value = DialogResult(ResultState.CANCELED)
}
- fun onDefaultOrNotSelected(providerName: String) {
+ fun onDefaultOrNotSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- selectedProvider = getProviderInfoByName(providerName)
)
// TODO: implement the if choose as default or not logic later
}
+
+ fun onPrimaryCreateOptionInfoSelected() {
+ var createOptionId = uiState.activeEntry?.activeCreateOptionInfo?.id
+ Log.d("Account Selector", "Option selected for creation: $createOptionId")
+ if (createOptionId != null) {
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ uiState.activeEntry?.activeProvider!!.name,
+ createOptionId
+ )
+ }
+ dialogResult.value = DialogResult(
+ ResultState.COMPLETE,
+ )
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
new file mode 100644
index 000000000000..d6f1b5f5c8e9
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+abstract class CredentialEntryUi(
+ val credentialTypeIcon: Icon,
+ val profileIcon: Icon?,
+ val lastUsedTimeMillis: Long?,
+ val note: CharSequence?,
+) {
+ companion object {
+ fun fromSlice(slice: Slice): CredentialEntryUi {
+ return when (slice.spec?.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> PasskeyCredentialEntryUi.fromSlice(slice)
+ TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntryUi.fromSlice(slice)
+ else -> throw IllegalArgumentException("Unexpected type: ${slice.spec?.type}")
+ }
+ }
+
+ const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
+ "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
+ const val TYPE_PASSWORD_CREDENTIAL: String = "androidx.credentials.TYPE_PASSWORD"
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
new file mode 100644
index 000000000000..bb3b206500b4
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+class PasskeyCredentialEntryUi(
+ val userName: CharSequence,
+ val userDisplayName: CharSequence?,
+ credentialTypeIcon: Icon,
+ profileIcon: Icon?,
+ lastUsedTimeMillis: Long?,
+ note: CharSequence?,
+) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
+ companion object {
+ fun fromSlice(slice: Slice): CredentialEntryUi {
+ var userName: CharSequence? = null
+ var userDisplayName: CharSequence? = null
+ var credentialTypeIcon: Icon? = null
+ var profileIcon: Icon? = null
+ var lastUsedTimeMillis: Long? = null
+ var note: CharSequence? = null
+
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_USER_NAME)) {
+ userName = it.text
+ } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
+ userDisplayName = it.text
+ } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
+ credentialTypeIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+ profileIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+ lastUsedTimeMillis = it.long
+ } else if (it.hasHint(Entry.HINT_NOTE)) {
+ note = it.text
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return PasskeyCredentialEntryUi(
+ userName!!, userDisplayName, credentialTypeIcon!!,
+ profileIcon, lastUsedTimeMillis, note,
+ )
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
new file mode 100644
index 000000000000..7311b7081343
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a password credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class PasswordCredentialEntryUi(
+ val userName: CharSequence,
+ val password: CharSequence,
+ credentialTypeIcon: Icon,
+ profileIcon: Icon?,
+ lastUsedTimeMillis: Long?,
+ note: CharSequence?,
+) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
+ companion object {
+ fun fromSlice(slice: Slice): CredentialEntryUi {
+ var userName: CharSequence? = null
+ var password: CharSequence? = null
+ var credentialTypeIcon: Icon? = null
+ var profileIcon: Icon? = null
+ var lastUsedTimeMillis: Long? = null
+ var note: CharSequence? = null
+
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_USER_NAME)) {
+ userName = it.text
+ } else if (it.hasHint(Entry.HINT_PASSWORD_VALUE)) {
+ password = it.text
+ } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
+ credentialTypeIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+ profileIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+ lastUsedTimeMillis = it.long
+ } else if (it.hasHint(Entry.HINT_NOTE)) {
+ note = it.text
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return PasswordCredentialEntryUi(
+ userName!!, password!!, credentialTypeIcon!!,
+ profileIcon, lastUsedTimeMillis, note,
+ )
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
new file mode 100644
index 000000000000..fad3309fb86f
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a save entry used during the create credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class SaveEntryUi(
+ val userProviderAccountName: CharSequence,
+ val credentialTypeIcon: Icon?,
+ val profileIcon: Icon?,
+ val passwordCount: Int?,
+ val passkeyCount: Int?,
+ val totalCredentialCount: Int?,
+ val lastUsedTimeMillis: Long?,
+) {
+ companion object {
+ fun fromSlice(slice: Slice): SaveEntryUi {
+ var userProviderAccountName: CharSequence? = null
+ var credentialTypeIcon: Icon? = null
+ var profileIcon: Icon? = null
+ var passwordCount: Int? = null
+ var passkeyCount: Int? = null
+ var totalCredentialCount: Int? = null
+ var lastUsedTimeMillis: Long? = null
+
+
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME)) {
+ userProviderAccountName = it.text
+ } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
+ credentialTypeIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+ profileIcon = it.icon
+ } else if (it.hasHint(Entry.HINT_PASSWORD_COUNT)) {
+ passwordCount = it.int
+ } else if (it.hasHint(Entry.HINT_PASSKEY_COUNT)) {
+ passkeyCount = it.int
+ } else if (it.hasHint(Entry.HINT_TOTAL_CREDENTIAL_COUNT)) {
+ totalCredentialCount = it.int
+ } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+ lastUsedTimeMillis = it.long
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return SaveEntryUi(
+ userProviderAccountName!!, credentialTypeIcon, profileIcon,
+ passwordCount, passkeyCount, totalCredentialCount, lastUsedTimeMillis,
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index acb22dac9854..4af25893ea37 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -25,6 +25,7 @@ import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
+import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
@@ -66,6 +67,7 @@ object GallerySpaEnvironment : SpaEnvironment() {
IllustrationPageProvider,
CategoryPageProvider,
ActionButtonPageProvider,
+ ProgressBarPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index e40775a95813..7fd49db93748 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -31,6 +31,7 @@ import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
+import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
@@ -54,6 +55,7 @@ object HomePageProvider : SettingsPageProvider {
IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
new file mode 100644
index 000000000000..dc45df4a0374
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.SystemUpdate
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ProgressBarPreference
+import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
+import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CircularLoadingBar
+import com.android.settingslib.spa.widget.ui.CircularProgressBar
+import com.android.settingslib.spa.widget.ui.LinearLoadingBar
+import kotlinx.coroutines.delay
+
+private const val TITLE = "Sample ProgressBar"
+
+object ProgressBarPageProvider : SettingsPageProvider {
+ override val name = "ProgressBar"
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ .setIsAllowSearch(true)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ // Mocks a loading time of 2 seconds.
+ var loading by remember { mutableStateOf(true) }
+ LaunchedEffect(Unit) {
+ delay(2000)
+ loading = false
+ }
+
+ RegularScaffold(title = TITLE) {
+ // Auto update the progress and finally jump tp 0.4f.
+ var progress by remember { mutableStateOf(0f) }
+ LaunchedEffect(Unit) {
+ delay(2000)
+ while (progress < 1f) {
+ delay(100)
+ progress += 0.01f
+ }
+ delay(500)
+ progress = 0.4f
+ }
+
+ // Show as a placeholder for progress bar
+ LargeProgressBar(progress)
+ // The remaining information only shows after loading complete.
+ if (!loading) {
+ SimpleProgressBar()
+ ProgressBarWithData()
+ CircularProgressBar(progress = progress, radius = 160f)
+ }
+ }
+
+ // Add loading bar examples, running for 2 seconds.
+ LinearLoadingBar(isLoading = loading, yOffset = 64.dp)
+ CircularLoadingBar(isLoading = loading)
+ }
+}
+
+@Composable
+private fun LargeProgressBar(progress: Float) {
+ ProgressBarPreference(object : ProgressBarPreferenceModel {
+ override val title = "Large Progress Bar"
+ override val progress = progress
+ override val height = 20f
+ })
+}
+
+@Composable
+private fun SimpleProgressBar() {
+ ProgressBarPreference(object : ProgressBarPreferenceModel {
+ override val title = "Simple Progress Bar"
+ override val progress = 0.2f
+ override val icon = Icons.Outlined.SystemUpdate
+ })
+}
+
+@Composable
+private fun ProgressBarWithData() {
+ ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel {
+ override val title = "Progress Bar with Data"
+ override val progress = 0.2f
+ override val icon = Icons.Outlined.Delete
+ }, data = "25G")
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ProgressBarPagePreview() {
+ SettingsTheme {
+ ProgressBarPageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 9a34dbf36735..6135203ec703 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -72,7 +72,7 @@ internal fun BaseLayout(
}
@Composable
-private fun BaseIcon(
+internal fun BaseIcon(
icon: @Composable (() -> Unit)?,
modifier: Modifier,
paddingStart: Dp,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt
new file mode 100644
index 000000000000..b8c59ad07cfd
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.LinearProgressBar
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+/**
+ * The widget model for [ProgressBarPreference] widget.
+ */
+interface ProgressBarPreferenceModel {
+ /**
+ * The title of this [ProgressBarPreference].
+ */
+ val title: String
+
+ /**
+ * The progress fraction of the ProgressBar. Should be float in range [0f, 1f]
+ */
+ val progress: Float
+
+ /**
+ * The icon image for [ProgressBarPreference]. If not specified, hides the icon by default.
+ */
+ val icon: ImageVector?
+ get() = null
+
+ /**
+ * The height of the ProgressBar.
+ */
+ val height: Float
+ get() = 4f
+
+ /**
+ * Indicates whether to use rounded corner for the progress bars.
+ */
+ val roundedCorner: Boolean
+ get() = true
+}
+
+/**
+ * Progress bar preference widget.
+ *
+ * Data is provided through [ProgressBarPreferenceModel].
+ */
+@Composable
+fun ProgressBarPreference(model: ProgressBarPreferenceModel) {
+ ProgressBarPreference(
+ title = model.title,
+ progress = model.progress,
+ icon = model.icon,
+ height = model.height,
+ roundedCorner = model.roundedCorner,
+ )
+}
+
+/**
+ * Progress bar with data preference widget.
+ */
+@Composable
+fun ProgressBarWithDataPreference(model: ProgressBarPreferenceModel, data: String) {
+ val icon = model.icon
+ ProgressBarWithDataPreference(
+ title = model.title,
+ data = data,
+ progress = model.progress,
+ icon = if (icon != null) ({
+ Icon(imageVector = icon, contentDescription = null)
+ }) else null,
+ height = model.height,
+ roundedCorner = model.roundedCorner,
+ )
+}
+
+@Composable
+internal fun ProgressBarPreference(
+ title: String,
+ progress: Float,
+ icon: ImageVector? = null,
+ height: Float = 4f,
+ roundedCorner: Boolean = true,
+) {
+ BaseLayout(
+ title = title,
+ subTitle = {
+ LinearProgressBar(progress, height, roundedCorner)
+ },
+ icon = if (icon != null) ({
+ Icon(imageVector = icon, contentDescription = null)
+ }) else null,
+ )
+}
+
+
+@Composable
+internal fun ProgressBarWithDataPreference(
+ title: String,
+ data: String,
+ progress: Float,
+ icon: (@Composable () -> Unit)? = null,
+ height: Float = 4f,
+ roundedCorner: Boolean = true,
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = SettingsDimension.itemPaddingEnd),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ BaseIcon(icon, Modifier, SettingsDimension.itemPaddingStart)
+ TitleWithData(
+ title = title,
+ data = data,
+ subTitle = {
+ LinearProgressBar(progress, height, roundedCorner)
+ },
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = SettingsDimension.itemPaddingVertical),
+ )
+ }
+}
+
+@Composable
+private fun TitleWithData(
+ title: String,
+ data: String,
+ subTitle: @Composable () -> Unit,
+ modifier: Modifier
+) {
+ Column(modifier) {
+ Row {
+ Box(modifier = Modifier.weight(1f)) {
+ SettingsTitle(title)
+ }
+ Text(
+ text = data,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ style = MaterialTheme.typography.titleMedium,
+ )
+ }
+ subTitle()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt
new file mode 100644
index 000000000000..1741f134f3d1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.absoluteOffset
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Indeterminate linear progress bar. Expresses an unspecified wait time.
+ */
+@Composable
+fun LinearLoadingBar(
+ isLoading: Boolean,
+ xOffset: Dp = 0.dp,
+ yOffset: Dp = 0.dp
+) {
+ if (isLoading) {
+ LinearProgressIndicator(
+ modifier = Modifier
+ .fillMaxWidth()
+ .absoluteOffset(xOffset, yOffset)
+ )
+ }
+}
+
+/**
+ * Indeterminate circular progress bar. Expresses an unspecified wait time.
+ */
+@Composable
+fun CircularLoadingBar(isLoading: Boolean) {
+ if (isLoading) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt
new file mode 100644
index 000000000000..5d8502db4986
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.progressSemantics
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.unit.dp
+
+/**
+ * Determinate linear progress bar. Displays the current progress of the whole process.
+ *
+ * Rounded corner is supported and enabled by default.
+ */
+@Composable
+fun LinearProgressBar(
+ progress: Float,
+ height: Float = 4f,
+ roundedCorner: Boolean = true
+) {
+ Box(modifier = Modifier.padding(top = 8.dp, bottom = 8.dp)) {
+ val color = MaterialTheme.colorScheme.onSurface
+ val trackColor = MaterialTheme.colorScheme.surfaceVariant
+ Canvas(
+ Modifier
+ .progressSemantics(progress)
+ .fillMaxWidth()
+ .height(height.dp)
+ ) {
+ drawLinearBarTrack(trackColor, roundedCorner)
+ drawLinearBar(progress, color, roundedCorner)
+ }
+ }
+}
+
+private fun DrawScope.drawLinearBar(
+ endFraction: Float,
+ color: Color,
+ roundedCorner: Boolean
+) {
+ val width = endFraction * size.width
+ drawRoundRect(
+ color = color,
+ size = Size(width, size.height),
+ cornerRadius = if (roundedCorner) CornerRadius(
+ size.height / 2,
+ size.height / 2
+ ) else CornerRadius.Zero,
+ )
+}
+
+private fun DrawScope.drawLinearBarTrack(
+ color: Color,
+ roundedCorner: Boolean
+) = drawLinearBar(1f, color, roundedCorner)
+
+/**
+ * Determinate circular progress bar. Displays the current progress of the whole process.
+ *
+ * Displayed in default material3 style, and rounded corner is not supported.
+ */
+@Composable
+fun CircularProgressBar(progress: Float, radius: Float = 40f) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator(
+ progress = progress,
+ modifier = Modifier.size(radius.dp, radius.dp)
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt
new file mode 100644
index 000000000000..5611f8ce14db
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ProgressBarPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ProgressBarPreference(object : ProgressBarPreferenceModel {
+ override val title = "Title"
+ override val progress = 0.2f
+ })
+ }
+ composeTestRule.onNodeWithText("Title").assertIsDisplayed()
+ }
+
+ @Test
+ fun data_displayed() {
+ composeTestRule.setContent {
+ ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel {
+ override val title = "Title"
+ override val progress = 0.2f
+ }, data = "Data")
+ }
+ composeTestRule.onNodeWithText("Title").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Data").assertIsDisplayed()
+ }
+
+ @Test
+ fun progressBar_displayed() {
+ composeTestRule.setContent {
+ ProgressBarPreference(object : ProgressBarPreferenceModel {
+ override val title = "Title"
+ override val progress = 0.2f
+ })
+ }
+
+ fun progressEqualsTo(progress: Float): SemanticsMatcher =
+ SemanticsMatcher.expectValue(
+ ProgressBarRangeInfo,
+ ProgressBarRangeInfo(progress, 0f..1f, 0)
+ )
+ composeTestRule.onNode(progressEqualsTo(0.2f)).assertIsDisplayed()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
index 7ae11758a0ce..3e5dd527a6b3 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
@@ -16,6 +16,9 @@
package com.android.settingslib.spa.widget.preference
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
+import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -41,5 +44,20 @@ class SliderPreferenceTest {
composeTestRule.onNodeWithText("Slider").assertIsDisplayed()
}
- // TODO: Add more unit tests for SliderPreference widget.
+ @Test
+ fun slider_displayed() {
+ composeTestRule.setContent {
+ SliderPreference(object : SliderPreferenceModel {
+ override val title = "Slider"
+ override val initValue = 40
+ })
+ }
+
+ fun progressEqualsTo(progress: Float): SemanticsMatcher =
+ SemanticsMatcher.expectValue(
+ ProgressBarRangeInfo,
+ ProgressBarRangeInfo(progress, 0f..100f, 0)
+ )
+ composeTestRule.onNode(progressEqualsTo(40f)).assertIsDisplayed()
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 91b852ab9f67..6641db1e6449 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -235,7 +235,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
/**
* @return whether high quality audio is enabled or not
*/
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
if (bluetoothDevice == null) {
@@ -287,7 +287,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
* @param device to get codec label from
* @return the label associated with the device codec
*/
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index 7275d6be99ad..1745379c3034 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -22,6 +22,7 @@ import static com.android.settingslib.enterprise.ActionDisabledLearnMoreButtonLa
import static com.android.settingslib.enterprise.ManagedDeviceActionDisabledByAdminController.DEFAULT_FOREGROUND_USER_CHECKER;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.ParentalControlsUtilsInternal;
@@ -45,6 +46,8 @@ public final class ActionDisabledByAdminControllerFactory {
return new BiometricActionDisabledByAdminController(stringProvider);
} else if (isFinancedDevice(context)) {
return new FinancedDeviceActionDisabledByAdminController(stringProvider);
+ } else if (isSupervisedDevice(context)) {
+ return new SupervisedDeviceActionDisabledByAdminController(stringProvider, restriction);
} else {
return new ManagedDeviceActionDisabledByAdminController(
stringProvider,
@@ -54,6 +57,15 @@ public final class ActionDisabledByAdminControllerFactory {
}
}
+ private static boolean isSupervisedDevice(Context context) {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ ComponentName supervisionComponent =
+ devicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ new UserHandle(UserHandle.myUserId()));
+ return supervisionComponent != null;
+ }
+
/**
* @return true if the restriction == UserManager.DISALLOW_BIOMETRIC and parental consent
* is required.
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
index 6e93494bf40e..714accc09763 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.net.Uri;
import android.provider.Settings;
import android.util.Log;
@@ -60,6 +61,10 @@ public class BiometricActionDisabledByAdminController extends BaseActionDisabled
final Intent intent = new Intent(Settings.ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING)
.putExtra(Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY,
Settings.SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS)
+ .setData(new Uri.Builder()
+ .scheme("policy")
+ .appendPath("biometric")
+ .build())
.setPackage(enforcedAdmin.component.getPackageName());
context.startActivity(intent);
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java
index b83837e6caf6..7ff91f8526fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java
@@ -79,6 +79,11 @@ public interface DeviceAdminStringProvider {
String getDisabledBiometricsParentConsentTitle();
/**
+ * Returns the dialog title when the setting is blocked by supervision app.
+ */
+ String getDisabledByParentContent();
+
+ /**
* Returns the dialog contents for when biometrics require parental consent.
*/
String getDisabledBiometricsParentConsentContent();
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminController.java
new file mode 100644
index 000000000000..815293e9c2a8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.settingslib.enterprise;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.jetbrains.annotations.Nullable;
+
+final class SupervisedDeviceActionDisabledByAdminController
+ extends BaseActionDisabledByAdminController {
+ private static final String TAG = "SupervisedDeviceActionDisabledByAdminController";
+ private final String mRestriction;
+
+ SupervisedDeviceActionDisabledByAdminController(
+ DeviceAdminStringProvider stringProvider, String restriction) {
+ super(stringProvider);
+ mRestriction = restriction;
+ }
+
+ @Override
+ public void setupLearnMoreButton(Context context) {
+
+ }
+
+ @Override
+ public String getAdminSupportTitle(@Nullable String restriction) {
+ return mStringProvider.getDisabledBiometricsParentConsentTitle();
+ }
+
+ @Override
+ public CharSequence getAdminSupportContentString(Context context,
+ @Nullable CharSequence supportMessage) {
+ return mStringProvider.getDisabledByParentContent();
+ }
+
+ @Nullable
+ @Override
+ public DialogInterface.OnClickListener getPositiveButtonListener(Context context,
+ RestrictedLockUtils.EnforcedAdmin enforcedAdmin) {
+ final Intent intent = new Intent(Settings.ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING)
+ .setData(new Uri.Builder()
+ .scheme("policy")
+ .appendPath("user_restrictions")
+ .appendPath(mRestriction)
+ .build())
+ .setPackage(enforcedAdmin.component.getPackageName());
+ ComponentName resolvedSupervisionActivity =
+ intent.resolveActivity(context.getPackageManager());
+ if (resolvedSupervisionActivity == null) {
+ return null;
+ }
+ return (dialog, which) -> {
+ Log.d(TAG, "Positive button clicked, component: " + enforcedAdmin.component);
+ context.startActivity(intent);
+ };
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
index 39977dfa5c80..f969a63dc663 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -41,19 +41,19 @@ public class MobileNetworkTypeIconsTest {
MobileNetworkTypeIcon icon =
MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
- assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.FOUR_G.name);
assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
}
@Test
public void getNetworkTypeIcon_unknown_returnsUnknown() {
- SignalIcon.MobileIconGroup unknownGroup =
- new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+ SignalIcon.MobileIconGroup unknownGroup = new SignalIcon.MobileIconGroup(
+ "testUnknownNameHere", /* dataContentDesc= */ 45, /* dataType= */ 6);
MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
- assertThat(icon.getIconResId()).isEqualTo(45);
- assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+ assertThat(icon.getIconResId()).isEqualTo(6);
+ assertThat(icon.getContentDescriptionResId()).isEqualTo(45);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
index 99e13c325472..1d5f1b20e31e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
@@ -32,6 +32,7 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider {
"default_disabled_by_policy_title_financed_device";
static final String DEFAULT_BIOMETRIC_TITLE = "biometric_title";
static final String DEFAULT_BIOMETRIC_CONTENTS = "biometric_contents";
+ static final String DISABLED_BY_PARENT_CONTENT = "disabled_by_parent_constent";
static final DeviceAdminStringProvider DEFAULT_DEVICE_ADMIN_STRING_PROVIDER =
new FakeDeviceAdminStringProvider(/* url = */ null);
@@ -97,6 +98,11 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider {
}
@Override
+ public String getDisabledByParentContent() {
+ return DISABLED_BY_PARENT_CONTENT;
+ }
+
+ @Override
public String getDisabledBiometricsParentConsentContent() {
return DEFAULT_BIOMETRIC_CONTENTS;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminControllerTest.java
new file mode 100644
index 000000000000..5d249c7f936c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/SupervisedDeviceActionDisabledByAdminControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.enterprise;
+
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ADMIN_COMPONENT;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
+import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DEVICE_ADMIN_STRING_PROVIDER;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.TestCase.assertEquals;
+
+import static org.mockito.Mockito.mock;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowResolveInfo;
+
+@RunWith(RobolectricTestRunner.class)
+public class SupervisedDeviceActionDisabledByAdminControllerTest {
+
+ private Context mContext;
+
+ private ActionDisabledByAdminControllerTestUtils mTestUtils;
+ private SupervisedDeviceActionDisabledByAdminController mController;
+
+ @Before
+ public void setUp() {
+ mContext = Robolectric.buildActivity(Activity.class).setup().get();
+
+ mTestUtils = new ActionDisabledByAdminControllerTestUtils();
+
+ mController = new SupervisedDeviceActionDisabledByAdminController(
+ DEFAULT_DEVICE_ADMIN_STRING_PROVIDER, UserManager.DISALLOW_ADD_USER);
+ mController.initialize(mTestUtils.createLearnMoreButtonLauncher());
+ mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
+ }
+
+ @Test
+ public void buttonClicked() {
+ Uri restrictionUri = Uri.parse("policy:/user_restrictions/no_add_user");
+ Intent intent = new Intent(Settings.ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING)
+ .setData(restrictionUri)
+ .setPackage(ADMIN_COMPONENT.getPackageName());
+ ResolveInfo resolveInfo = ShadowResolveInfo.newResolveInfo("Admin Activity",
+ ADMIN_COMPONENT.getPackageName(), "InfoActivity");
+ shadowOf(mContext.getPackageManager()).addResolveInfoForIntent(intent, resolveInfo);
+
+ DialogInterface.OnClickListener listener =
+ mController.getPositiveButtonListener(mContext, ENFORCED_ADMIN);
+ assertNotNull("Supervision controller must supply a non-null listener", listener);
+ listener.onClick(mock(DialogInterface.class), 0 /* which */);
+
+ Intent nextIntent = shadowOf(RuntimeEnvironment.application).getNextStartedActivity();
+ assertEquals(Settings.ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING,
+ nextIntent.getAction());
+ assertEquals(restrictionUri, nextIntent.getData());
+ assertEquals(ADMIN_COMPONENT.getPackageName(), nextIntent.getPackage());
+ }
+
+ @Test
+ public void noButton() {
+ // No supervisor restricted setting Activity
+ DialogInterface.OnClickListener listener =
+ mController.getPositiveButtonListener(mContext, ENFORCED_ADMIN);
+ assertNull("Supervision controller generates null listener", listener);
+ }
+}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 3623c7821dfe..edea3abc0618 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -311,4 +311,7 @@
<!-- Whether tilt to bright is enabled by default. -->
<bool name="def_wearable_tiltToBrightEnabled">false</bool>
+
+ <!-- Whether vibrate icon is shown in the status bar by default. -->
+ <integer name="def_statusBarVibrateIconEnabled">0</integer>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3a25d85b5ecf..ccbfac226c46 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3659,7 +3659,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 210;
+ private static final int SETTINGS_VERSION = 211;
private final int mUserId;
@@ -5531,7 +5531,17 @@ public class SettingsProvider extends ContentProvider {
// removed now that feature is enabled for everyone
currentVersion = 210;
}
-
+ if (currentVersion == 210) {
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final int defaultValueVibrateIconEnabled = getContext().getResources()
+ .getInteger(R.integer.def_statusBarVibrateIconEnabled);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ String.valueOf(defaultValueVibrateIconEnabled),
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ currentVersion = 211;
+ }
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9747a6c5ca70..aea2f5235201 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -817,7 +817,8 @@ public class SettingsBackupTest {
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
- Settings.Secure.UI_TRANSLATION_ENABLED);
+ Settings.Secure.UI_TRANSLATION_ENABLED,
+ Settings.Secure.CREDENTIAL_SERVICE);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fecf1241acf8..90fab08ed43e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -154,6 +154,7 @@
<uses-permission android:name="android.permission.CONTROL_UI_TRACING" />
<uses-permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+ <uses-permission android:name="android.permission.KILL_ALL_BACKGROUND_PROCESSES" />
<!-- Internal permissions granted to the shell. -->
<uses-permission android:name="android.permission.FORCE_BACK" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
@@ -210,6 +211,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.CREATE_USERS" />
+ <uses-permission android:name="android.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION" />
<uses-permission android:name="android.permission.QUERY_USERS" />
<uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" />
<uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
@@ -715,6 +717,9 @@
<!-- Permission required for CTS test - ActivityPermissionRationaleTest -->
<uses-permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY" />
+ <!-- Permission required for CTS test - CtsDeviceLockTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index ca36fa43da76..fdfad2bc2fa1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -25,7 +25,6 @@ import android.graphics.Rect
import android.os.Looper
import android.util.Log
import android.util.MathUtils
-import android.view.GhostView
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -86,6 +85,9 @@ constructor(
*/
val sourceIdentity: Any
+ /** The CUJ associated to this controller. */
+ val cuj: DialogCuj?
+
/**
* Move the drawing of the source in the overlay of [viewGroup].
*
@@ -142,7 +144,31 @@ constructor(
* controlled by this controller.
*/
// TODO(b/252723237): Make this non-nullable
- fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
+ fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder?
+
+ companion object {
+ /**
+ * Create a [Controller] that can animate [source] to and from a dialog.
+ *
+ * Important: The view must be attached to a [ViewGroup] when calling this function and
+ * during the animation. For safety, this method will return null when it is not.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be
+ * properly animated.
+ */
+ fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+ if (source.parent !is ViewGroup) {
+ Log.e(
+ TAG,
+ "Skipping animation as view $source is not attached to a ViewGroup",
+ Exception(),
+ )
+ return null
+ }
+
+ return ViewDialogLaunchAnimatorController(source, cuj)
+ }
+ }
}
/**
@@ -172,7 +198,12 @@ constructor(
cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
- show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+ val controller = Controller.fromView(view, cuj)
+ if (controller == null) {
+ dialog.show()
+ } else {
+ show(dialog, controller, animateBackgroundBoundsChange)
+ }
}
/**
@@ -187,10 +218,10 @@ constructor(
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
*/
+ @JvmOverloads
fun show(
dialog: Dialog,
controller: Controller,
- cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
@@ -207,7 +238,10 @@ constructor(
it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
val animateFrom =
- animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
+ animatedParent?.dialogContentWithBackground?.let {
+ Controller.fromView(it, controller.cuj)
+ }
+ ?: controller
if (animatedParent == null && animateFrom !is LaunchableView) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -244,96 +278,12 @@ constructor(
animateBackgroundBoundsChange,
animatedParent,
isForTesting,
- cuj,
)
openedDialogs.add(animatedDialog)
animatedDialog.start()
}
- /** Create a [Controller] that can animate [source] to & from a dialog. */
- private fun createController(source: View): Controller {
- return object : Controller {
- override val viewRoot: ViewRootImpl
- get() = source.viewRootImpl
-
- override val sourceIdentity: Any = source
-
- override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
- // Create a temporary ghost of the source (which will make it invisible) and add it
- // to the host dialog.
- GhostView.addGhost(source, viewGroup)
-
- // The ghost of the source was just created, so the source is currently invisible.
- // We need to make sure that it stays invisible as long as the dialog is shown or
- // animating.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
- }
-
- override fun stopDrawingInOverlay() {
- // Note: here we should remove the ghost from the overlay, but in practice this is
- // already done by the launch controllers created below.
-
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
- source.visibility = View.VISIBLE
- }
-
- override fun createLaunchController(): LaunchAnimator.Controller {
- val delegate = GhostedViewLaunchAnimatorController(source)
- return object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
- // ghost (that ghosts only the source content, and not its background) will
- // be added right after this by the delegate and will be animated.
- GhostView.removeGhost(source)
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
- }
-
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-
- // We hide the source when the dialog is showing. We will make this view
- // visible again when dismissing the dialog. This does nothing if the source
- // implements [LaunchableView], as it's already INVISIBLE in that case.
- source.visibility = View.INVISIBLE
- }
- }
- }
-
- override fun createExitController(): LaunchAnimator.Controller {
- return GhostedViewLaunchAnimatorController(source)
- }
-
- override fun shouldAnimateExit(): Boolean {
- // The source should be invisible by now, if it's not then something else changed
- // its visibility and we probably don't want to run the animation.
- if (source.visibility != View.INVISIBLE) {
- return false
- }
-
- return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
- }
-
- override fun onExitAnimationCancelled() {
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- // If the view is invisible it's probably because of us, so we make it visible
- // again.
- if (source.visibility == View.INVISIBLE) {
- source.visibility = View.VISIBLE
- }
- }
-
- override fun jankConfigurationBuilder(
- cuj: Int
- ): InteractionJankMonitor.Configuration.Builder? {
- return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
- }
- }
- }
-
/**
* Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
* allow for dismissing the whole stack.
@@ -563,9 +513,6 @@ private class AnimatedDialog(
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
private val forceDisableSynchronization: Boolean,
-
- /** Interaction to which the dialog animation is associated. */
- private val cuj: DialogCuj? = null
) {
/**
* The DecorView of this dialog window.
@@ -618,8 +565,9 @@ private class AnimatedDialog(
private var hasInstrumentedJank = false
fun start() {
+ val cuj = controller.cuj
if (cuj != null) {
- val config = controller.jankConfigurationBuilder(cuj.cujType)
+ val config = controller.jankConfigurationBuilder()
if (config != null) {
if (cuj.tag != null) {
config.setTag(cuj.tag)
@@ -917,7 +865,7 @@ private class AnimatedDialog(
}
if (hasInstrumentedJank) {
- interactionJankMonitor.end(cuj!!.cujType)
+ interactionJankMonitor.end(controller.cuj!!.cujType)
}
}
)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 8ce372dbb278..40a5e9794d37 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -30,7 +30,12 @@ interface Expandable {
*/
fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
- // TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
+ /**
+ * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
+ * a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is
+ * currently not attached or visible).
+ */
+ fun dialogLaunchController(cuj: DialogCuj? = null): DialogLaunchAnimator.Controller?
companion object {
/**
@@ -39,6 +44,7 @@ interface Expandable {
* Note: The background of [view] should be a (rounded) rectangle so that it can be properly
* animated.
*/
+ @JvmStatic
fun fromView(view: View): Expandable {
return object : Expandable {
override fun activityLaunchController(
@@ -46,6 +52,12 @@ interface Expandable {
): ActivityLaunchAnimator.Controller? {
return ActivityLaunchAnimator.Controller.fromView(view, cujType)
}
+
+ override fun dialogLaunchController(
+ cuj: DialogCuj?
+ ): DialogLaunchAnimator.Controller? {
+ return DialogLaunchAnimator.Controller.fromView(view, cuj)
+ }
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
new file mode 100644
index 000000000000..ecee598afe4e
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.animation
+
+import android.view.GhostView
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import com.android.internal.jank.InteractionJankMonitor
+
+/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
+class ViewDialogLaunchAnimatorController
+internal constructor(
+ private val source: View,
+ override val cuj: DialogCuj?,
+) : DialogLaunchAnimator.Controller {
+ override val viewRoot: ViewRootImpl
+ get() = source.viewRootImpl
+
+ override val sourceIdentity: Any = source
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ // Create a temporary ghost of the source (which will make it invisible) and add it
+ // to the host dialog.
+ GhostView.addGhost(source, viewGroup)
+
+ // The ghost of the source was just created, so the source is currently invisible.
+ // We need to make sure that it stays invisible as long as the dialog is shown or
+ // animating.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ }
+
+ override fun stopDrawingInOverlay() {
+ // Note: here we should remove the ghost from the overlay, but in practice this is
+ // already done by the launch controllers created below.
+
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+ source.visibility = View.VISIBLE
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = GhostedViewLaunchAnimatorController(source)
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+ // ghost (that ghosts only the source content, and not its background) will
+ // be added right after this by the delegate and will be animated.
+ GhostView.removeGhost(source)
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // We hide the source when the dialog is showing. We will make this view
+ // visible again when dismissing the dialog. This does nothing if the source
+ // implements [LaunchableView], as it's already INVISIBLE in that case.
+ source.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ return GhostedViewLaunchAnimatorController(source)
+ }
+
+ override fun shouldAnimateExit(): Boolean {
+ // The source should be invisible by now, if it's not then something else changed
+ // its visibility and we probably don't want to run the animation.
+ if (source.visibility != View.INVISIBLE) {
+ return false
+ }
+
+ return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+ }
+
+ override fun onExitAnimationCancelled() {
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+ // If the view is invisible it's probably because of us, so we make it visible
+ // again.
+ if (source.visibility == View.INVISIBLE) {
+ source.visibility = View.VISIBLE
+ }
+ }
+
+ override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
+ val type = cuj?.cujType ?: return null
+ return InteractionJankMonitor.Configuration.Builder.withView(type, source)
+ }
+}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 9671adde4904..40580d29380b 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -47,6 +47,10 @@ java_test_host {
"tests/**/*.kt",
"tests/**/*.java",
],
+ data: [
+ ":framework",
+ ":androidx.annotation_annotation",
+ ],
static_libs: [
"SystemUILintChecker",
"junit",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4eeeb850292a..4b9aa13c0240 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -32,7 +32,8 @@ import org.jetbrains.uast.UReferenceExpression
class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
override fun getApplicableReferenceNames(): List<String> {
- return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+ return mutableListOf(
+ "ALPHA_8", "RGB_565", "ARGB_4444", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
}
override fun visitReference(
@@ -40,13 +41,12 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
reference: UReferenceExpression,
referenced: PsiElement
) {
-
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
ISSUE,
referenced,
- context.getNameLocation(referenced),
+ context.getNameLocation(reference),
"Replace software bitmap with `Config.HARDWARE`"
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index d4c55c0d9149..141dd0535986 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -18,185 +18,22 @@ package com.android.internal.systemui.lint
import com.android.annotations.NonNull
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
+import java.io.File
import org.intellij.lang.annotations.Language
@Suppress("UnstableApiUsage")
@NonNull
private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
-internal val commonSettingsCode =
- """
-public static float getFloat(ContentResolver cr, String name) { return 0.0f; }
-public static long getLong(ContentResolver cr, String name) {
- return 0L;
-}
-public static int getInt(ContentResolver cr, String name) {
- return 0;
-}
-public static String getString(ContentResolver cr, String name) {
- return "";
-}
-public static float getFloat(ContentResolver cr, String name, float def) {
- return 0.0f;
-}
-public static long getLong(ContentResolver cr, String name, long def) {
- return 0L;
-}
-public static int getInt(ContentResolver cr, String name, int def) {
- return 0;
-}
-public static String getString(ContentResolver cr, String name, String def) {
- return "";
-}
-public static boolean putFloat(ContentResolver cr, String name, float value) {
- return true;
-}
-public static boolean putLong(ContentResolver cr, String name, long value) {
- return true;
-}
-public static boolean putInt(ContentResolver cr, String name, int value) {
- return true;
-}
-public static boolean putFloat(ContentResolver cr, String name) {
- return true;
-}
-public static boolean putString(ContentResolver cr, String name, String value) {
- return true;
-}
-"""
-
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
* stubs are not used in the lint detectors themselves.
*/
internal val androidStubs =
arrayOf(
- indentedJava(
- """
-package android.app;
-
-public class ActivityManager {
- public static int getCurrentUser() {}
-}
-"""
- ),
- indentedJava(
- """
-package android.accounts;
-
-public class AccountManager {
- public static AccountManager get(Context context) { return null; }
-}
-"""
- ),
- indentedJava(
- """
-package android.os;
-import android.content.pm.UserInfo;
-import android.annotation.UserIdInt;
-
-public class UserManager {
- public UserInfo getUserInfo(@UserIdInt int userId) {}
-}
-"""
- ),
- indentedJava("""
-package android.annotation;
-
-public @interface UserIdInt {}
-"""),
- indentedJava("""
-package android.content.pm;
-
-public class UserInfo {}
-"""),
- indentedJava("""
-package android.os;
-
-public class Looper {}
-"""),
- indentedJava("""
-package android.os;
-
-public class Handler {}
-"""),
- indentedJava("""
-package android.content;
-
-public class ServiceConnection {}
-"""),
- indentedJava("""
-package android.os;
-
-public enum UserHandle {
- ALL
-}
-"""),
- indentedJava(
- """
-package android.content;
-import android.os.UserHandle;
-import android.os.Handler;
-import android.os.Looper;
-import java.util.concurrent.Executor;
-
-public class Context {
- public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
- public void registerReceiverAsUser(
- BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
- String broadcastPermission, Handler scheduler) {}
- public void registerReceiverForAllUsers(
- BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
- Handler scheduler) {}
- public void sendBroadcast(Intent intent) {}
- public void sendBroadcast(Intent intent, String receiverPermission) {}
- public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
- public void bindService(Intent intent) {}
- public void bindServiceAsUser(
- Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
- public void unbindService(ServiceConnection connection) {}
- public Looper getMainLooper() { return null; }
- public Executor getMainExecutor() { return null; }
- public Handler getMainThreadHandler() { return null; }
- public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
- public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
-}
-"""
- ),
- indentedJava(
- """
-package android.app;
-import android.content.Context;
-
-public class Activity extends Context {}
-"""
- ),
- indentedJava(
- """
-package android.graphics;
-
-public class Bitmap {
- public enum Config {
- ARGB_8888,
- RGB_565,
- HARDWARE
- }
- public static Bitmap createBitmap(int width, int height, Config config) {
- return null;
- }
-}
-"""
- ),
- indentedJava("""
-package android.content;
-
-public class BroadcastReceiver {}
-"""),
- indentedJava("""
-package android.content;
-
-public class IntentFilter {}
-"""),
+ LibraryReferenceTestFile(File("framework.jar").canonicalFile),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
indentedJava(
"""
package com.android.systemui.settings;
@@ -208,47 +45,4 @@ public interface UserTracker {
}
"""
),
- indentedJava(
- """
-package androidx.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-@Retention(SOURCE)
-@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
-public @interface WorkerThread {
-}
-"""
- ),
- indentedJava(
- """
-package android.provider;
-
-public class Settings {
- public static final class Global {
- public static final String UNLOCK_SOUND = "unlock_sound";
- """ +
- commonSettingsCode +
- """
- }
- public static final class Secure {
- """ +
- commonSettingsCode +
- """
- }
- public static final class System {
- """ +
- commonSettingsCode +
- """
- }
-}
-"""
- ),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 090ddf88fa3c..c632636eb9c8 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -51,12 +51,12 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
.run()
.expect(
"""
- src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
- ARGB_8888,
- ~~~~~~~~~
- src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
- RGB_565,
- ~~~~~~~
+ src/TestClass.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ ~~~~~~~
+ src/TestClass.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ ~~~~~~~~~
0 errors, 2 warnings
"""
)
@@ -67,7 +67,7 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
lint()
.files(
TestFiles.java(
- """
+ """
import android.graphics.Bitmap;
public class TestClass {
@@ -76,8 +76,7 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
}
}
"""
- )
- .indented(),
+ ),
*stubs
)
.issues(SoftwareBitmapDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 2183b3805eed..3f93f075fe8b 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -3,9 +3,42 @@ package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestLintTask
import java.io.File
+import org.junit.ClassRule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
@Suppress("UnstableApiUsage")
+@RunWith(JUnit4::class)
abstract class SystemUILintDetectorTest : LintDetectorTest() {
+
+ companion object {
+ @ClassRule
+ @JvmField
+ val libraryChecker: LibraryExists =
+ LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ }
+
+ class LibraryExists(vararg val libraryNames: String) : TestRule {
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ for (libName in libraryNames) {
+ val libFile = File(libName)
+ if (!libFile.canonicalFile.exists()) {
+ throw Exception(
+ "Could not find $libName in the test's working directory. " +
+ "File ${libFile.absolutePath} does not exist."
+ )
+ }
+ }
+ base.evaluate()
+ }
+ }
+ }
+ }
/**
* Customize the lint task to disable SDK usage completely. This ensures that running the tests
* in Android Studio has the same result as running the tests in atest
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 065c3149c2f5..50c3d7e1e76b 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -40,17 +40,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.roundToInt
-/** A controller that can control animated launches. */
+/** A controller that can control animated launches from an [Expandable]. */
interface ExpandableController {
- /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
- fun forActivity(): ActivityLaunchAnimator.Controller
-
- /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
- fun forDialog(): DialogLaunchAnimator.Controller
+ /** The [Expandable] controlled by this controller. */
+ val expandable: Expandable
}
/**
@@ -120,13 +119,26 @@ internal class ExpandableControllerImpl(
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
- override fun forActivity(): ActivityLaunchAnimator.Controller {
- return activityController()
- }
+ override val expandable: Expandable =
+ object : Expandable {
+ override fun activityLaunchController(
+ cujType: Int?,
+ ): ActivityLaunchAnimator.Controller? {
+ if (!isComposed.value) {
+ return null
+ }
- override fun forDialog(): DialogLaunchAnimator.Controller {
- return dialogController()
- }
+ return activityController(cujType)
+ }
+
+ override fun dialogLaunchController(cuj: DialogCuj?): DialogLaunchAnimator.Controller? {
+ if (!isComposed.value) {
+ return null
+ }
+
+ return dialogController(cuj)
+ }
+ }
/**
* Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
@@ -233,7 +245,7 @@ internal class ExpandableControllerImpl(
}
/** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
- private fun activityController(): ActivityLaunchAnimator.Controller {
+ private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
val delegate = launchController()
return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -248,10 +260,11 @@ internal class ExpandableControllerImpl(
}
}
- private fun dialogController(): DialogLaunchAnimator.Controller {
+ private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
return object : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
override val sourceIdentity: Any = this@ExpandableControllerImpl
+ override val cuj: DialogCuj? = cuj
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
val newOverlay = viewGroup.overlay as ViewGroupOverlay
@@ -294,9 +307,7 @@ internal class ExpandableControllerImpl(
isDialogShowing.value = false
}
- override fun jankConfigurationBuilder(
- cuj: Int
- ): InteractionJankMonitor.Configuration.Builder? {
+ override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
// TODO(b/252723237): Add support for jank monitoring when animating from a
// Composable.
return null
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index d0d3052bc544..31ab24748c93 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -832,7 +832,6 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index b3dd95553ed0..dee0f5cd1979 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -205,6 +205,13 @@ enum class Style(internal val coreSpec: CoreSpec) {
n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
)),
+ MONOCHROMATIC(CoreSpec(
+ a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ n2 = TonalSpec(HueSource(), ChromaConstant(.0))
+ )),
}
class ColorScheme(
@@ -219,7 +226,7 @@ class ColorScheme(
val neutral1: List<Int>
val neutral2: List<Int>
- constructor(@ColorInt seed: Int, darkTheme: Boolean):
+ constructor(@ColorInt seed: Int, darkTheme: Boolean) :
this(seed, darkTheme, Style.TONAL_SPOT)
@JvmOverloads
@@ -227,7 +234,7 @@ class ColorScheme(
wallpaperColors: WallpaperColors,
darkTheme: Boolean,
style: Style = Style.TONAL_SPOT
- ):
+ ) :
this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
val allAccentColors: List<Int>
@@ -472,4 +479,4 @@ class ColorScheme(
return huePopulation
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
new file mode 100644
index 000000000000..de0e526a97c3
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size android:height="@dimen/bouncer_user_switcher_popup_items_divider_height"/>
+ <solid android:color="@color/user_switcher_fullscreen_bg"/>
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 46f6ab2399d1..0a55cf779683 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -119,6 +119,7 @@
<dimen name="bouncer_user_switcher_width">248dp</dimen>
<dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
<dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
+ <dimen name="bouncer_user_switcher_popup_items_divider_height">2dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index bc8e540cb612..3bcc37a478c9 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -16,45 +16,47 @@
<com.android.systemui.biometrics.AuthCredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPinPasswordStyle">
<RelativeLayout
android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
+ style="?headerStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:id="@+id/icon"
- style="@style/TextAppearance.AuthNonBioCredential.Icon"
+ style="?headerIconStyle"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:contentDescription="@null"/>
<TextView
android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
+ style="?titleTextAppearance"
android:layout_below="@id/icon"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ style="?subTitleTextAppearance"
android:layout_below="@id/title"
android:layout_alignParentLeft="true"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
+ style="?descriptionTextAppearance"
android:layout_below="@id/subtitle"
android:layout_alignParentLeft="true"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
@@ -67,7 +69,7 @@
<ImeAwareEditText
android:id="@+id/lockPassword"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ style="?passwordTextAppearance"
android:layout_width="208dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -77,7 +79,7 @@
<TextView
android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
+ style="?errorTextAppearance"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 19a85fec1397..a3dd334bd667 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -16,91 +16,71 @@
<com.android.systemui.biometrics.AuthCredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPatternStyle">
- <LinearLayout
+ <RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center"
- android:orientation="vertical">
-
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1">
<ImageView
android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
<TextView
android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
<TextView
android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
<TextView
android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ </RelativeLayout>
+
+ <FrameLayout
+ android:layout_weight="1"
+ style="?containerStyle"
+ android:layout_width="0dp"
+ android:layout_height="match_parent">
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
<TextView
android:id="@+id/error"
+ style="?errorTextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
-
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
-
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
+ android:layout_gravity="center_horizontal|bottom"/>
- </LinearLayout>
+ </FrameLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 75a80bc39a1f..774b335f913e 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -16,43 +16,45 @@
<com.android.systemui.biometrics.AuthCredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="@dimen/biometric_dialog_elevation"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:theme="?app:attr/lockPinPasswordStyle">
<RelativeLayout
android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
+ style="?headerStyle"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/icon"
- style="@style/TextAppearance.AuthNonBioCredential.Icon"
+ style="?headerIconStyle"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:contentDescription="@null"/>
<TextView
android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
+ style="?titleTextAppearance"
android:layout_below="@id/icon"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ style="?subTitleTextAppearance"
android:layout_below="@id/title"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
+ style="?descriptionTextAppearance"
android:layout_below="@id/subtitle"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
@@ -64,7 +66,7 @@
<ImeAwareEditText
android:id="@+id/lockPassword"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ style="?passwordTextAppearance"
android:layout_width="208dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -74,7 +76,7 @@
<TextView
android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
+ style="?errorTextAppearance"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index dada9813c320..4af997017bba 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -16,87 +16,66 @@
<com.android.systemui.biometrics.AuthCredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPatternStyle">
<RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:contentDescription="@null" />
-
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/icon"
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/title"
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/description"
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </RelativeLayout>
- <TextView
- android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </LinearLayout>
+ <FrameLayout
+ android:id="@+id/auth_credential_container"
+ style="?containerStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <LinearLayout
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/auth_credential_header"
- android:gravity="center"
- android:orientation="vertical"
- android:paddingBottom="16dp"
- android:paddingTop="60dp">
+ android:layout_height="match_parent"/>
- <FrameLayout
- style="@style/LockPatternContainerStyle"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1">
-
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- style="@style/LockPatternStyle"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center" />
-
- </FrameLayout>
-
- </LinearLayout>
-
- <LinearLayout
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true">
-
- <TextView
- android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- </RelativeLayout>
+ android:layout_gravity="center_horizontal|bottom"/>
+ </FrameLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 4da77118f00b..bc97e511e7f4 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -19,12 +19,12 @@
<com.android.systemui.temporarydisplay.chipbar.ChipbarRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/media_ttt_sender_chip"
+ android:id="@+id/chipbar_root_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
- android:id="@+id/media_ttt_sender_chip_inner"
+ android:id="@+id/chipbar_inner"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -39,7 +39,7 @@
>
<com.android.internal.widget.CachingIconView
- android:id="@+id/app_icon"
+ android:id="@+id/start_icon"
android:layout_width="@dimen/media_ttt_app_icon_size"
android:layout_height="@dimen/media_ttt_app_icon_size"
android:layout_marginEnd="12dp"
@@ -69,7 +69,7 @@
/>
<ImageView
- android:id="@+id/failure_icon"
+ android:id="@+id/error"
android:layout_width="@dimen/media_ttt_status_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
@@ -78,11 +78,11 @@
android:alpha="0.0"
/>
+ <!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. -->
<TextView
- android:id="@+id/undo"
+ android:id="@+id/end_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/media_transfer_undo"
android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
android:textSize="@dimen/media_ttt_text_size"
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index ac9a947f4417..aefd9981d02e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -24,7 +24,36 @@
<item name="android:paddingEnd">24dp</item>
<item name="android:paddingTop">48dp</item>
<item name="android:paddingBottom">10dp</item>
- <item name="android:gravity">top|center_horizontal</item>
+ <item name="android:gravity">top|left</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">320dp</item>
+ <item name="android:maxWidth">320dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingVertical">20dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">36dp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">18sp</item>
</style>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
new file mode 100644
index 000000000000..8148d3dfaf7d
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">120dp</item>
+ <item name="android:paddingVertical">40dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
new file mode 100644
index 000000000000..771de08cb360
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">120dp</item>
+ <item name="android:paddingEnd">120dp</item>
+ <item name="android:paddingTop">80dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">180dp</item>
+ <item name="android:paddingVertical">80dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
new file mode 100644
index 000000000000..f9ed67d89de7
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">120dp</item>
+ <item name="android:paddingVertical">40dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
new file mode 100644
index 000000000000..78d299c483e6
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">120dp</item>
+ <item name="android:paddingEnd">120dp</item>
+ <item name="android:paddingTop">80dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">240dp</item>
+ <item name="android:paddingVertical">120dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 9a71995383ac..df0659d67afe 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -191,5 +191,18 @@
<declare-styleable name="DelayableMarqueeTextView">
<attr name="marqueeDelay" format="integer" />
</declare-styleable>
+
+ <declare-styleable name="AuthCredentialView">
+ <attr name="lockPatternStyle" format="reference" />
+ <attr name="lockPinPasswordStyle" format="reference" />
+ <attr name="containerStyle" format="reference" />
+ <attr name="headerStyle" format="reference" />
+ <attr name="headerIconStyle" format="reference" />
+ <attr name="titleTextAppearance" format="reference" />
+ <attr name="subTitleTextAppearance" format="reference" />
+ <attr name="descriptionTextAppearance" format="reference" />
+ <attr name="passwordTextAppearance" format="reference" />
+ <attr name="errorTextAppearance" format="reference"/>
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 637ac1911a85..d4d8843acdea 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -641,7 +641,7 @@
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_correction_label">Color correction</string>
<!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
- <string name="quick_settings_more_user_settings">User settings</string>
+ <string name="quick_settings_more_user_settings">Manage users</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="quick_settings_done">Done</string>
<!-- QuickSettings: Control panel: Label for button that dismisses user switcher control panel. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 475ca919c3bf..e76887babc50 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -195,15 +195,11 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
- <style name="TextAppearance.AuthNonBioCredential.Icon">
- <item name="android:layout_width">@dimen/biometric_auth_icon_size</item>
- <item name="android:layout_height">@dimen/biometric_auth_icon_size</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
- <item name="android:layout_marginTop">20dp</item>
- <item name="android:textSize">36sp</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36dp</item>
+ <item name="android:focusable">true</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
@@ -215,12 +211,10 @@
<style name="TextAppearance.AuthNonBioCredential.Description">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">20dp</item>
- <item name="android:textSize">16sp</item>
+ <item name="android:textSize">18sp</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Error">
- <item name="android:paddingTop">6dp</item>
- <item name="android:paddingBottom">18dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/colorError</item>
@@ -239,12 +233,33 @@
<style name="AuthCredentialHeaderStyle">
<item name="android:paddingStart">48dp</item>
<item name="android:paddingEnd">48dp</item>
- <item name="android:paddingTop">28dp</item>
- <item name="android:paddingBottom">20dp</item>
- <item name="android:orientation">vertical</item>
+ <item name="android:paddingTop">48dp</item>
+ <item name="android:paddingBottom">10dp</item>
<item name="android:layout_gravity">top</item>
</style>
+ <style name="AuthCredentialIconStyle">
+ <item name="android:layout_width">@dimen/biometric_auth_icon_size</item>
+ <item name="android:layout_height">@dimen/biometric_auth_icon_size</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:padding">20dp</item>
+ </style>
+
+ <style name="AuthCredentialPinPasswordContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">48dp</item>
+ <item name="android:maxWidth">600dp</item>
+ <item name="android:minHeight">48dp</item>
+ <item name="android:minWidth">200dp</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -282,7 +297,9 @@
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
<item name="wallpaperTextColorAccent">@color/material_dynamic_primary90</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
- <item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
+ <item name="*android:lockPatternStyle">@style/LockPatternViewStyle</item>
+ <item name="lockPatternStyle">@style/LockPatternContainerStyle</item>
+ <item name="lockPinPasswordStyle">@style/LockPinPasswordContainerStyle</item>
<item name="passwordStyle">@style/PasswordTheme</item>
<item name="numPadKeyStyle">@style/NumPadKey</item>
<item name="backgroundProtectedStyle">@style/BackgroundProtectedStyle</item>
@@ -308,27 +325,33 @@
<item name="android:textColor">?attr/wallpaperTextColor</item>
</style>
- <style name="LockPatternContainerStyle">
- <item name="android:maxHeight">400dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">0dp</item>
- <item name="android:minWidth">0dp</item>
- <item name="android:paddingHorizontal">60dp</item>
- <item name="android:paddingBottom">40dp</item>
+ <style name="AuthCredentialStyle">
+ <item name="*android:regularColor">?android:attr/colorForeground</item>
+ <item name="*android:successColor">?android:attr/colorForeground</item>
+ <item name="*android:errorColor">?android:attr/colorError</item>
+ <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+ <item name="headerStyle">@style/AuthCredentialHeaderStyle</item>
+ <item name="headerIconStyle">@style/AuthCredentialIconStyle</item>
+ <item name="titleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Title</item>
+ <item name="subTitleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Subtitle</item>
+ <item name="descriptionTextAppearance">@style/TextAppearance.AuthNonBioCredential.Description</item>
+ <item name="passwordTextAppearance">@style/TextAppearance.AuthCredential.PasswordEntry</item>
+ <item name="errorTextAppearance">@style/TextAppearance.AuthNonBioCredential.Error</item>
</style>
- <style name="LockPatternStyle">
+ <style name="LockPatternViewStyle" >
<item name="*android:regularColor">?android:attr/colorAccent</item>
<item name="*android:successColor">?android:attr/textColorPrimary</item>
<item name="*android:errorColor">?android:attr/colorError</item>
<item name="*android:dotColor">?android:attr/textColorSecondary</item>
</style>
- <style name="LockPatternStyleBiometricPrompt">
- <item name="*android:regularColor">?android:attr/colorForeground</item>
- <item name="*android:successColor">?android:attr/colorForeground</item>
- <item name="*android:errorColor">?android:attr/colorError</item>
- <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+ <style name="LockPatternContainerStyle" parent="@style/AuthCredentialStyle">
+ <item name="containerStyle">@style/AuthCredentialPatternContainerStyle</item>
+ </style>
+
+ <style name="LockPinPasswordContainerStyle" parent="@style/AuthCredentialStyle">
+ <item name="containerStyle">@style/AuthCredentialPinPasswordContainerStyle</item>
</style>
<style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 2e391c7aacbe..49cc48321d77 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.testing.screenshot
import android.app.Activity
import android.graphics.Color
import android.view.View
+import android.view.Window
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
@@ -51,13 +52,14 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR
/**
* Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
- * the context of [emulationSpec].
+ * the context of [emulationSpec]. Window must be specified to capture views that render
+ * hardware buffers.
*/
- fun screenshotTest(goldenIdentifier: String, view: View) {
+ fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
view.removeElevationRecursively()
ScreenshotRuleAsserter.Builder(screenshotRule)
- .setScreenshotProvider { view.toBitmap() }
+ .setScreenshotProvider { view.toBitmap(window) }
.withMatcher(matcher)
.build()
.assertGoldenImage(goldenIdentifier)
@@ -94,6 +96,6 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR
activity.currentFocus?.clearFocus()
}
- screenshotTest(goldenIdentifier, rootView)
+ screenshotTest(goldenIdentifier, rootView, activity.window)
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 134f3bc93847..1cf7c503a508 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -190,8 +190,13 @@ class AnimatableClockView @JvmOverloads constructor(
override fun onDraw(canvas: Canvas) {
lastDraw = getTimestamp()
- // intentionally doesn't call super.onDraw here or else the text will be rendered twice
- textAnimator?.draw(canvas)
+ // Use textAnimator to render text if animation is enabled.
+ // Otherwise default to using standard draw functions.
+ if (isAnimationEnabled) {
+ textAnimator?.draw(canvas)
+ } else {
+ super.onDraw(canvas)
+ }
}
override fun invalidate() {
@@ -363,6 +368,9 @@ class AnimatableClockView @JvmOverloads constructor(
onAnimationEnd = onAnimationEnd
)
textAnimator?.glyphFilter = glyphFilter
+ if (color != null && !isAnimationEnabled) {
+ setTextColor(color)
+ }
} else {
// when the text animator is set, update its start values
onTextAnimatorInitialized = Runnable {
@@ -377,6 +385,9 @@ class AnimatableClockView @JvmOverloads constructor(
onAnimationEnd = onAnimationEnd
)
textAnimator?.glyphFilter = glyphFilter
+ if (color != null && !isAnimationEnabled) {
+ setTextColor(color)
+ }
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index e3c21cca2263..cd272635905b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,7 +21,6 @@ import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
import android.util.Log
-import com.android.systemui.dagger.qualifiers.Main
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
@@ -31,7 +30,6 @@ import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.google.gson.Gson
-import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private const val DEBUG = true
@@ -43,13 +41,6 @@ open class ClockRegistry(
val handler: Handler,
defaultClockProvider: ClockProvider
) {
- @Inject constructor(
- context: Context,
- pluginManager: PluginManager,
- @Main handler: Handler,
- defaultClockProvider: DefaultClockProvider
- ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
-
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 72f8b7b09dca..40c8774d4f34 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -1,13 +1,16 @@
package com.android.systemui.shared.recents.utilities;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.view.Surface;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.util.SplitBounds;
/**
* Utility class to position the thumbnail in the TaskView
@@ -16,10 +19,26 @@ public class PreviewPositionHelper {
public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ * TODO(b/254378592): Remove after consolidation
+ */
+ public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ * TODO(b/254378592): Remove after consolidation
+ */
+ public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
// Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
private final RectF mClippedInsets = new RectF();
private final Matrix mMatrix = new Matrix();
private boolean mIsOrientationChanged;
+ private SplitBounds mSplitBounds;
+ private int mDesiredStagePosition;
public Matrix getMatrix() {
return mMatrix;
@@ -33,6 +52,11 @@ public class PreviewPositionHelper {
return mIsOrientationChanged;
}
+ public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) {
+ mSplitBounds = splitBounds;
+ mDesiredStagePosition = desiredStagePosition;
+ }
+
/**
* Updates the matrix based on the provided parameters
*/
@@ -42,10 +66,19 @@ public class PreviewPositionHelper {
boolean isRotated = false;
boolean isOrientationDifferent;
+ float fullscreenTaskWidth = screenWidthPx;
+ if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
+ // For landscape, scale the width
+ float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.leftTaskPercent
+ : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+ // Scale landscape width to that of actual screen
+ fullscreenTaskWidth = screenWidthPx * taskPercent;
+ }
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+ float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
@@ -180,7 +213,7 @@ public class PreviewPositionHelper {
* portrait or vice versa, {@code false} otherwise
*/
private boolean isOrientationChange(int deltaRotation) {
- return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270;
}
private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
@@ -189,13 +222,13 @@ public class PreviewPositionHelper {
mMatrix.setRotate(90 * deltaRotate);
switch (deltaRotate) { /* Counter-clockwise */
- case Surface.ROTATION_90:
+ case ROTATION_90:
translateX = thumbnailPosition.height();
break;
- case Surface.ROTATION_270:
+ case ROTATION_270:
translateY = thumbnailPosition.width();
break;
- case Surface.ROTATION_180:
+ case ROTATION_180:
translateX = thumbnailPosition.width();
translateY = thumbnailPosition.height();
break;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 5d6598d63a1b..8a2509610310 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -51,6 +51,8 @@ public final class InteractionJankMonitorWrapper {
InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+ public static final int CUJ_RECENTS_SCROLLING =
+ InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -59,7 +61,8 @@ public final class InteractionJankMonitorWrapper {
CUJ_APP_CLOSE_TO_PIP,
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
- CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION
+ CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+ CUJ_RECENTS_SCROLLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 5277e40492e4..450784ea8f03 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -70,7 +70,7 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
}
override fun setMessage(msg: CharSequence?) {
- if (msg == textAboutToShow || msg == text) {
+ if ((msg == textAboutToShow && msg != null) || msg == text) {
return
}
textAboutToShow = msg
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 9151238499dc..910955a45f7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -23,12 +23,18 @@ import android.content.res.Resources
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REGION_SAMPLING
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -38,13 +44,20 @@ import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
open class ClockEventController @Inject constructor(
- private val statusBarStateController: StatusBarStateController,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
private val batteryController: BatteryController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -53,7 +66,7 @@ open class ClockEventController @Inject constructor(
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags
) {
var clock: ClockController? = null
set(value) {
@@ -70,9 +83,9 @@ open class ClockEventController @Inject constructor(
private var isCharging = false
private var dozeAmount = 0f
private var isKeyguardVisible = false
-
- private val regionSamplingEnabled =
- featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
+ private var isRegistered = false
+ private var disposableHandle: DisposableHandle? = null
+ private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private fun updateColors() {
if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
@@ -165,15 +178,6 @@ open class ClockEventController @Inject constructor(
}
}
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- clock?.animations?.doze(linear)
-
- isDozing = linear > dozeAmount
- dozeAmount = linear
- }
- }
-
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
@@ -195,13 +199,11 @@ open class ClockEventController @Inject constructor(
}
}
- init {
- isDozing = statusBarStateController.isDozing
- }
-
- fun registerListeners() {
- dozeAmount = statusBarStateController.dozeAmount
- isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+ fun registerListeners(parent: View) {
+ if (isRegistered) {
+ return
+ }
+ isRegistered = true
broadcastDispatcher.registerReceiver(
localeBroadcastReceiver,
@@ -210,17 +212,28 @@ open class ClockEventController @Inject constructor(
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.addCallback(statusBarStateListener)
smallRegionSampler?.startRegionSampler()
largeRegionSampler?.startRegionSampler()
+ disposableHandle = parent.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ listenForDozing(this)
+ listenForDozeAmount(this)
+ listenForDozeAmountTransition(this)
+ }
+ }
}
fun unregisterListeners() {
+ if (!isRegistered) {
+ return
+ }
+ isRegistered = false
+
+ disposableHandle?.dispose()
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
configurationController.removeCallback(configListener)
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.removeCallback(statusBarStateListener)
smallRegionSampler?.stopRegionSampler()
largeRegionSampler?.stopRegionSampler()
}
@@ -235,8 +248,39 @@ open class ClockEventController @Inject constructor(
largeRegionSampler?.dump(pw)
}
- companion object {
- private val TAG = ClockEventController::class.simpleName
- private const val FORMAT_NUMBER = 1234567890
+ @VisibleForTesting
+ internal fun listenForDozeAmount(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardInteractor.dozeAmount.collect {
+ dozeAmount = it
+ clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor.aodToLockscreenTransition.collect {
+ // Would eventually run this:
+ // dozeAmount = it.value
+ // clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozing(scope: CoroutineScope): Job {
+ return scope.launch {
+ combine (
+ keyguardInteractor.dozeAmount,
+ keyguardInteractor.isDozing,
+ ) { localDozeAmount, localIsDozing ->
+ localDozeAmount > dozeAmount || localIsDozing
+ }
+ .collect { localIsDozing ->
+ isDozing = localIsDozing
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 20d064b960d2..8eebe30222ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -165,7 +165,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
- mClockEventController.registerListeners();
+ mClockEventController.registerListeners(mView);
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index bcd1a1ee2696..81305f90e2b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -219,13 +219,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
};
- private SwipeListener mSwipeListener = new SwipeListener() {
+ private final SwipeListener mSwipeListener = new SwipeListener() {
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- mUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
- showMessage(null, null);
+ if (didFaceAuthRun) {
+ showMessage(null, null);
+ }
}
if (mUpdateMonitor.isFaceEnrolled()) {
mUpdateMonitor.requestActiveUnlock(
@@ -234,7 +237,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
};
- private ConfigurationController.ConfigurationListener mConfigurationListener =
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onThemeChanged() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f5582761c0ae..aff9dcbc26e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -106,7 +106,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -2352,11 +2351,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param userInitiatedRequest true if the user explicitly requested face auth
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
+ * @return current face auth detection state, true if it is running.
*/
- public void requestFaceAuth(boolean userInitiatedRequest,
+ public boolean requestFaceAuth(boolean userInitiatedRequest,
@FaceAuthApiRequestReason String reason) {
mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
+ return isFaceDetectionRunning();
}
/**
@@ -2366,10 +2367,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER);
}
- public boolean isFaceScanning() {
- return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
- }
-
private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
@@ -2417,7 +2414,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Attempts to trigger active unlock from trust agent.
*/
private void requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
String reason,
boolean dismissKeyguard
) {
@@ -2447,7 +2444,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Only dismisses the keyguard under certain conditions.
*/
public void requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
String extraReason
) {
final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
@@ -2714,7 +2711,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return shouldListen;
}
- private void maybeLogListenerModelData(KeyguardListenModel model) {
+ private void maybeLogListenerModelData(@NonNull KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
if (model instanceof KeyguardActiveUnlockModel) {
@@ -3796,4 +3793,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
mListenModels.print(pw);
}
+
+ /**
+ * Schedules a watchdog for the face and fingerprint BiometricScheduler.
+ * Cancels all operations in the scheduler if it is hung for 10 seconds.
+ */
+ public void startBiometricWatchdog() {
+ if (mFaceManager != null) {
+ mFaceManager.scheduleWatchdog();
+ }
+ if (mFpm != null) {
+ mFpm.scheduleWatchdog();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
index c4be1ba53503..72a44bd198f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
@@ -21,9 +21,14 @@ import java.util.List;
import dagger.Module;
import dagger.Provides;
-/** Dagger Module for clock package. */
+/**
+ * Dagger Module for clock package.
+ *
+ * @deprecated Migrate to ClockRegistry
+ */
@Module
-public abstract class ClockModule {
+@Deprecated
+public abstract class ClockInfoModule {
/** */
@Provides
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
new file mode 100644
index 000000000000..f43f559b4234
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -0,0 +1,45 @@
+/*
+ * 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.keyguard.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.clocks.DefaultClockProvider;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clocks. */
+@Module
+public abstract class ClockRegistryModule {
+ /** Provide the ClockRegistry as a singleton so that it is not instantiated more than once. */
+ @Provides
+ @SysUISingleton
+ public static ClockRegistry getClockRegistry(
+ @Application Context context,
+ PluginManager pluginManager,
+ @Main Handler handler,
+ DefaultClockProvider defaultClockProvider) {
+ return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 82b32cf616ec..2f79e30a0b5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -51,7 +51,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
- fun logActiveUnlockTriggered(reason: String) {
+ fun logActiveUnlockTriggered(reason: String?) {
logBuffer.log("ActiveUnlock", DEBUG,
{ str1 = reason },
{ "initiate active unlock triggerReason=$str1" })
@@ -101,14 +101,14 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "Face authenticated for wrong user: $int1" })
}
- fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+ fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String?) {
logBuffer.log(TAG, DEBUG, {
int1 = msgId
str1 = helpMsg
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
logBuffer.log(TAG, DEBUG, {
bool1 = userInitiatedRequest
str1 = reason
@@ -187,7 +187,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "No Profile Owner or Device Owner supervision app found for User $int1" })
}
- fun logPhoneStateChanged(newState: String) {
+ fun logPhoneStateChanged(newState: String?) {
logBuffer.log(TAG, DEBUG,
{ str1 = newState },
{ "handlePhoneStateChanged($str1)" })
@@ -240,7 +240,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
}
- fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+ fun logServiceStateIntent(action: String?, serviceState: ServiceState?, subId: Int) {
logBuffer.log(TAG, VERBOSE, {
str1 = action
str2 = "$serviceState"
@@ -256,7 +256,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
}
- fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+ fun logSimStateFromIntent(action: String?, extraSimState: String?, slotId: Int, subId: Int) {
logBuffer.log(TAG, VERBOSE, {
str1 = action
str2 = extraSimState
@@ -289,7 +289,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "SubInfo:$str1" })
}
- fun logTimeFormatChanged(newTimeFormat: String) {
+ fun logTimeFormatChanged(newTimeFormat: String?) {
logBuffer.log(TAG, DEBUG,
{ str1 = newTimeFormat },
{ "handleTimeFormatUpdate timeFormat=$str1" })
@@ -338,18 +338,18 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
fun logUserRequestedUnlock(
requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
- reason: String,
+ reason: String?,
dismissKeyguard: Boolean
) {
logBuffer.log("ActiveUnlock", DEBUG, {
- str1 = requestOrigin.name
+ str1 = requestOrigin?.name
str2 = reason
bool1 = dismissKeyguard
}, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
}
fun logShowTrustGrantedMessage(
- message: String
+ message: String?
) {
logBuffer.log(TAG, DEBUG, {
str1 = message
diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java
index 652595100c0f..73fdce6c9045 100644
--- a/packages/SystemUI/src/com/android/systemui/Dumpable.java
+++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java
@@ -30,7 +30,6 @@ public interface Dumpable {
/**
* Called when it's time to dump the internal state
- * @param fd A file descriptor.
* @param pw Where to write your dump to.
* @param args Arguments.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index c5955860aebf..3e0fa455d39e 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -19,6 +19,7 @@ package com.android.systemui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
+import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
@@ -55,7 +56,7 @@ class FaceScanningOverlay(
private val rimRect = RectF()
private var cameraProtectionColor = Color.BLACK
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
- com.android.systemui.R.attr.wallpaperTextColorAccent)
+ R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
var hideOverlayRunnable: Runnable? = null
var faceAuthSucceeded = false
@@ -84,46 +85,19 @@ class FaceScanningOverlay(
}
override fun drawCutoutProtection(canvas: Canvas) {
- if (rimProgress > HIDDEN_RIM_SCALE && !protectionRect.isEmpty) {
- val rimPath = Path(protectionPath)
- val scaleMatrix = Matrix().apply {
- val rimBounds = RectF()
- rimPath.computeBounds(rimBounds, true)
- setScale(rimProgress, rimProgress, rimBounds.centerX(), rimBounds.centerY())
- }
- rimPath.transform(scaleMatrix)
- rimPaint.style = Paint.Style.FILL
- val rimPaintAlpha = rimPaint.alpha
- rimPaint.color = ColorUtils.blendARGB(
- faceScanningAnimColor,
- Color.WHITE,
- statusBarStateController.dozeAmount)
- rimPaint.alpha = rimPaintAlpha
- canvas.drawPath(rimPath, rimPaint)
+ if (protectionRect.isEmpty) {
+ return
}
-
- if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
- !protectionRect.isEmpty) {
- val scaledProtectionPath = Path(protectionPath)
- val scaleMatrix = Matrix().apply {
- val protectionPathRect = RectF()
- scaledProtectionPath.computeBounds(protectionPathRect, true)
- setScale(cameraProtectionProgress, cameraProtectionProgress,
- protectionPathRect.centerX(), protectionPathRect.centerY())
- }
- scaledProtectionPath.transform(scaleMatrix)
- paint.style = Paint.Style.FILL
- paint.color = cameraProtectionColor
- canvas.drawPath(scaledProtectionPath, paint)
+ if (rimProgress > HIDDEN_RIM_SCALE) {
+ drawFaceScanningRim(canvas)
+ }
+ if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE) {
+ drawCameraProtection(canvas)
}
- }
-
- override fun updateVisOnUpdateCutout(): Boolean {
- return false // instead, we always update the visibility whenever face scanning starts/ends
}
override fun enableShowProtection(show: Boolean) {
- val showScanningAnimNow = keyguardUpdateMonitor.isFaceScanning && show
+ val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
if (showScanningAnimNow == showScanningAnim) {
return
}
@@ -152,91 +126,26 @@ class FaceScanningOverlay(
if (showScanningAnim) Interpolators.STANDARD_ACCELERATE
else if (faceAuthSucceeded) Interpolators.STANDARD
else Interpolators.STANDARD_DECELERATE
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- cameraProtectionProgress = animation.animatedValue as Float
- invalidate()
- })
+ addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
cameraProtectionAnimator = null
if (!showScanningAnim) {
- visibility = View.INVISIBLE
- hideOverlayRunnable?.run()
- hideOverlayRunnable = null
- requestLayout()
+ hide()
}
}
})
}
rimAnimator?.cancel()
- rimAnimator = AnimatorSet().apply {
- if (showScanningAnim) {
- val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
- PULSE_RADIUS_OUT).apply {
- duration = PULSE_APPEAR_DURATION
- interpolator = Interpolators.STANDARD_DECELERATE
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
-
- // animate in camera protection, rim, and then pulse in/out
- playSequentially(cameraProtectionAnimator, rimAppearAnimator,
- createPulseAnimator(), createPulseAnimator(),
- createPulseAnimator(), createPulseAnimator(),
- createPulseAnimator(), createPulseAnimator())
- } else {
- val rimDisappearAnimator = ValueAnimator.ofFloat(
- rimProgress,
- if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS
- else SHOW_CAMERA_PROTECTION_SCALE
- ).apply {
- duration =
- if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
- else PULSE_ERROR_DISAPPEAR_DURATION
- interpolator =
- if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE
- else Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rimProgress = HIDDEN_RIM_SCALE
- invalidate()
- }
- })
- }
- if (faceAuthSucceeded) {
- val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
- duration = PULSE_SUCCESS_DISAPPEAR_DURATION
- interpolator = Interpolators.LINEAR
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimPaint.alpha = animation.animatedValue as Int
- invalidate()
- })
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rimPaint.alpha = 255
- invalidate()
- }
- })
- }
- val rimSuccessAnimator = AnimatorSet()
- rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
- playTogether(rimSuccessAnimator, cameraProtectionAnimator)
- } else {
- playTogether(rimDisappearAnimator, cameraProtectionAnimator)
- }
- }
-
+ rimAnimator = if (showScanningAnim) {
+ createFaceScanningRimAnimator()
+ } else if (faceAuthSucceeded) {
+ createFaceSuccessRimAnimator()
+ } else {
+ createFaceNotSuccessRimAnimator()
+ }
+ rimAnimator?.apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
rimAnimator = null
@@ -245,34 +154,12 @@ class FaceScanningOverlay(
}
}
})
- start()
}
+ rimAnimator?.start()
}
- fun createPulseAnimator(): AnimatorSet {
- return AnimatorSet().apply {
- val pulseInwards = ValueAnimator.ofFloat(
- PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
- duration = PULSE_DURATION_INWARDS
- interpolator = Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
- val pulseOutwards = ValueAnimator.ofFloat(
- PULSE_RADIUS_IN, PULSE_RADIUS_OUT).apply {
- duration = PULSE_DURATION_OUTWARDS
- interpolator = Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
- playSequentially(pulseInwards, pulseOutwards)
- }
+ override fun updateVisOnUpdateCutout(): Boolean {
+ return false // instead, we always update the visibility whenever face scanning starts/ends
}
override fun updateProtectionBoundingPath() {
@@ -290,17 +177,153 @@ class FaceScanningOverlay(
// Make sure that our measured height encompasses the extra space for the animation
mTotalBounds.union(mBoundingRect)
mTotalBounds.union(
- rimRect.left.toInt(),
- rimRect.top.toInt(),
- rimRect.right.toInt(),
- rimRect.bottom.toInt())
+ rimRect.left.toInt(),
+ rimRect.top.toInt(),
+ rimRect.right.toInt(),
+ rimRect.bottom.toInt())
setMeasuredDimension(
- resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+ resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
} else {
setMeasuredDimension(
- resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+ resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+ }
+ }
+
+ private fun drawFaceScanningRim(canvas: Canvas) {
+ val rimPath = Path(protectionPath)
+ scalePath(rimPath, rimProgress)
+ rimPaint.style = Paint.Style.FILL
+ val rimPaintAlpha = rimPaint.alpha
+ rimPaint.color = ColorUtils.blendARGB(
+ faceScanningAnimColor,
+ Color.WHITE,
+ statusBarStateController.dozeAmount
+ )
+ rimPaint.alpha = rimPaintAlpha
+ canvas.drawPath(rimPath, rimPaint)
+ }
+
+ private fun drawCameraProtection(canvas: Canvas) {
+ val scaledProtectionPath = Path(protectionPath)
+ scalePath(scaledProtectionPath, cameraProtectionProgress)
+ paint.style = Paint.Style.FILL
+ paint.color = cameraProtectionColor
+ canvas.drawPath(scaledProtectionPath, paint)
+ }
+
+ private fun createFaceSuccessRimAnimator(): AnimatorSet {
+ val rimSuccessAnimator = AnimatorSet()
+ rimSuccessAnimator.playTogether(
+ createRimDisappearAnimator(
+ PULSE_RADIUS_SUCCESS,
+ PULSE_SUCCESS_DISAPPEAR_DURATION,
+ Interpolators.STANDARD_DECELERATE
+ ),
+ createSuccessOpacityAnimator(),
+ )
+ return AnimatorSet().apply {
+ playTogether(rimSuccessAnimator, cameraProtectionAnimator)
+ }
+ }
+
+ private fun createFaceNotSuccessRimAnimator(): AnimatorSet {
+ return AnimatorSet().apply {
+ playTogether(
+ createRimDisappearAnimator(
+ SHOW_CAMERA_PROTECTION_SCALE,
+ PULSE_ERROR_DISAPPEAR_DURATION,
+ Interpolators.STANDARD
+ ),
+ cameraProtectionAnimator,
+ )
+ }
+ }
+
+ private fun createRimDisappearAnimator(
+ endValue: Float,
+ animDuration: Long,
+ timeInterpolator: TimeInterpolator
+ ): ValueAnimator {
+ return ValueAnimator.ofFloat(rimProgress, endValue).apply {
+ duration = animDuration
+ interpolator = timeInterpolator
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimProgress = HIDDEN_RIM_SCALE
+ invalidate()
+ }
+ })
+ }
+ }
+
+ private fun createSuccessOpacityAnimator(): ValueAnimator {
+ return ValueAnimator.ofInt(255, 0).apply {
+ duration = PULSE_SUCCESS_DISAPPEAR_DURATION
+ interpolator = Interpolators.LINEAR
+ addUpdateListener(this@FaceScanningOverlay::updateRimAlpha)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimPaint.alpha = 255
+ invalidate()
+ }
+ })
+ }
+ }
+
+ private fun createFaceScanningRimAnimator(): AnimatorSet {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ createPulseAnimator()
+ )
+ }
+ }
+
+ private fun createRimAppearAnimator(): ValueAnimator {
+ return ValueAnimator.ofFloat(
+ SHOW_CAMERA_PROTECTION_SCALE,
+ PULSE_RADIUS_OUT
+ ).apply {
+ duration = PULSE_APPEAR_DURATION
+ interpolator = Interpolators.STANDARD_DECELERATE
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+ }
+ }
+
+ private fun hide() {
+ visibility = INVISIBLE
+ hideOverlayRunnable?.run()
+ hideOverlayRunnable = null
+ requestLayout()
+ }
+
+ private fun updateRimProgress(animator: ValueAnimator) {
+ rimProgress = animator.animatedValue as Float
+ invalidate()
+ }
+
+ private fun updateCameraProtectionProgress(animator: ValueAnimator) {
+ cameraProtectionProgress = animator.animatedValue as Float
+ invalidate()
+ }
+
+ private fun updateRimAlpha(animator: ValueAnimator) {
+ rimPaint.alpha = animator.animatedValue as Int
+ invalidate()
+ }
+
+ private fun createPulseAnimator(): ValueAnimator {
+ return ValueAnimator.ofFloat(
+ PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
+ duration = HALF_PULSE_DURATION
+ interpolator = Interpolators.STANDARD
+ repeatCount = 11 // Pulse inwards and outwards, reversing direction, 6 times
+ repeatMode = ValueAnimator.REVERSE
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
}
}
@@ -363,13 +386,24 @@ class FaceScanningOverlay(
private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L
private const val PULSE_APPEAR_DURATION = 250L // without start delay
- private const val PULSE_DURATION_INWARDS = 500L
- private const val PULSE_DURATION_OUTWARDS = 500L
+ private const val HALF_PULSE_DURATION = 500L
private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L
private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay
private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
+
+ private fun scalePath(path: Path, scalingFactor: Float) {
+ val scaleMatrix = Matrix().apply {
+ val boundingRectangle = RectF()
+ path.computeBounds(boundingRectangle, true)
+ setScale(
+ scalingFactor, scalingFactor,
+ boundingRectangle.centerX(), boundingRectangle.centerY()
+ )
+ }
+ path.transform(scaleMatrix)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
new file mode 100644
index 000000000000..4c3a7ff4e2eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
@@ -0,0 +1,7 @@
+package com.android.systemui
+
+import com.android.systemui.dump.nano.SystemUIProtoDump
+
+interface ProtoDumpable : Dumpable {
+ fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 7bcba3cc1c46..50e03992df49 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -121,6 +121,6 @@ public class SystemUIService extends Service {
DumpHandler.PRIORITY_ARG_CRITICAL};
}
- mDumpHandler.dump(pw, massagedArgs);
+ mDumpHandler.dump(fd, pw, massagedArgs);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
new file mode 100644
index 000000000000..d6d039903505
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -0,0 +1,378 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.util.MathUtils.constrain;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.HashMap;
+
+/**
+ * Controls the interaction animations of the {@link MenuView}. Also, it will use the relative
+ * coordinate based on the {@link MenuViewLayer} to compute the offset of the {@link MenuView}.
+ */
+class MenuAnimationController {
+ private static final String TAG = "MenuAnimationController";
+ private static final boolean DEBUG = false;
+ private static final float MIN_PERCENT = 0.0f;
+ private static final float MAX_PERCENT = 1.0f;
+ private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float FLING_FRICTION_SCALAR = 1.9f;
+ private static final float DEFAULT_FRICTION = 4.2f;
+ private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
+ private static final float SPRING_STIFFNESS = 700f;
+ private static final float ESCAPE_VELOCITY = 750f;
+
+ private static final int FADE_OUT_DURATION_MS = 1000;
+ private static final int FADE_EFFECT_DURATION_MS = 3000;
+
+ private final MenuView mMenuView;
+ private final ValueAnimator mFadeOutAnimator;
+ private final Handler mHandler;
+ private boolean mIsMovedToEdge;
+ private boolean mIsFadeEffectEnabled;
+
+ // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
+ // DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
+ private final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
+ new HashMap<>();
+
+ MenuAnimationController(MenuView menuView) {
+ mMenuView = menuView;
+
+ mHandler = createUiHandler();
+ mFadeOutAnimator = new ValueAnimator();
+ mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
+ mFadeOutAnimator.addUpdateListener(
+ (animation) -> menuView.setAlpha((float) animation.getAnimatedValue()));
+ }
+
+ void moveToPosition(PointF position) {
+ moveToPositionX(position.x);
+ moveToPositionY(position.y);
+ }
+
+ void moveToPositionX(float positionX) {
+ DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX);
+ }
+
+ private void moveToPositionY(float positionY) {
+ DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY);
+ }
+
+ void moveToPositionYIfNeeded(float positionY) {
+ // If the list view was out of screen bounds, it would allow users to nest scroll inside
+ // and avoid conflicting with outer scroll.
+ final RecyclerView listView = (RecyclerView) mMenuView.getChildAt(/* index= */ 0);
+ if (listView.getOverScrollMode() == View.OVER_SCROLL_NEVER) {
+ moveToPositionY(positionY);
+ }
+ }
+
+ void moveToTopLeftPosition() {
+ mIsMovedToEdge = false;
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.top));
+ }
+
+ void moveToTopRightPosition() {
+ mIsMovedToEdge = false;
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.top));
+ }
+
+ void moveToBottomLeftPosition() {
+ mIsMovedToEdge = false;
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.bottom));
+ }
+
+ void moveToBottomRightPosition() {
+ mIsMovedToEdge = false;
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.bottom));
+ }
+
+ void moveAndPersistPosition(PointF position) {
+ moveToPosition(position);
+ mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
+ constrainPositionAndUpdate(position);
+ }
+
+ void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
+ final boolean shouldMenuFlingLeft = isOnLeftSide()
+ ? velocityX < ESCAPE_VELOCITY
+ : velocityX < -ESCAPE_VELOCITY;
+
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ final float finalPositionX = shouldMenuFlingLeft
+ ? draggableBounds.left : draggableBounds.right;
+
+ final float minimumVelocityToReachEdge =
+ (finalPositionX - x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION);
+
+ final float startXVelocity = shouldMenuFlingLeft
+ ? Math.min(minimumVelocityToReachEdge, velocityX)
+ : Math.max(minimumVelocityToReachEdge, velocityX);
+
+ flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
+ startXVelocity,
+ FLING_FRICTION_SCALAR,
+ new SpringForce()
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+ finalPositionX);
+
+ flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
+ velocityY,
+ FLING_FRICTION_SCALAR,
+ new SpringForce()
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+ /* finalPosition= */ null);
+ }
+
+ private void flingThenSpringMenuWith(DynamicAnimation.ViewProperty property, float velocity,
+ float friction, SpringForce spring, Float finalPosition) {
+
+ final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
+ final float currentValue = menuPositionProperty.getValue(mMenuView);
+ final Rect bounds = mMenuView.getMenuDraggableBounds();
+ final float min =
+ property.equals(DynamicAnimation.TRANSLATION_X)
+ ? bounds.left
+ : bounds.top;
+ final float max =
+ property.equals(DynamicAnimation.TRANSLATION_X)
+ ? bounds.right
+ : bounds.bottom;
+
+ final FlingAnimation flingAnimation = new FlingAnimation(mMenuView, menuPositionProperty);
+ flingAnimation.setFriction(friction)
+ .setStartVelocity(velocity)
+ .setMinValue(Math.min(currentValue, min))
+ .setMaxValue(Math.max(currentValue, max))
+ .addEndListener((animation, canceled, endValue, endVelocity) -> {
+ if (canceled) {
+ if (DEBUG) {
+ Log.d(TAG, "The fling animation was canceled.");
+ }
+
+ return;
+ }
+
+ final float endPosition = finalPosition != null
+ ? finalPosition
+ : Math.max(min, Math.min(max, endValue));
+ springMenuWith(property, spring, endVelocity, endPosition);
+ });
+
+ cancelAnimation(property);
+ mPositionAnimations.put(property, flingAnimation);
+ flingAnimation.start();
+ }
+
+ private void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
+ float velocity, float finalPosition) {
+ final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
+ final SpringAnimation springAnimation =
+ new SpringAnimation(mMenuView, menuPositionProperty)
+ .setSpring(spring)
+ .addEndListener((animation, canceled, endValue, endVelocity) -> {
+ if (canceled || endValue != finalPosition) {
+ return;
+ }
+
+ onSpringAnimationEnd(new PointF(mMenuView.getTranslationX(),
+ mMenuView.getTranslationY()));
+ })
+ .setStartVelocity(velocity);
+
+ cancelAnimation(property);
+ mPositionAnimations.put(property, springAnimation);
+ springAnimation.animateToFinalPosition(finalPosition);
+ }
+
+ /**
+ * Determines whether to hide the menu to the edge of the screen with the given current
+ * translation x of the menu view. It should be used when receiving the action up touch event.
+ *
+ * @param currentXTranslation the current translation x of the menu view.
+ * @return true if the menu would be hidden to the edge, otherwise false.
+ */
+ boolean maybeMoveToEdgeAndHide(float currentXTranslation) {
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+
+ // If the translation x is zero, it should be at the left of the bound.
+ if (currentXTranslation < draggableBounds.left
+ || currentXTranslation > draggableBounds.right) {
+ moveToEdgeAndHide();
+ return true;
+ }
+
+ fadeOutIfEnabled();
+ return false;
+ }
+
+ private boolean isOnLeftSide() {
+ return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX();
+ }
+
+ boolean isMovedToEdge() {
+ return mIsMovedToEdge;
+ }
+
+ void moveToEdgeAndHide() {
+ mIsMovedToEdge = true;
+
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top,
+ draggableBounds.bottom);
+ final float menuHalfWidth = mMenuView.getWidth() / 2.0f;
+ final float endX = isOnLeftSide()
+ ? draggableBounds.left - menuHalfWidth
+ : draggableBounds.right + menuHalfWidth;
+ moveAndPersistPosition(new PointF(endX, endY));
+
+ // Keep the touch region let users could click extra space to pop up the menu view
+ // from the screen edge
+ mMenuView.onBoundsInParentChanged(isOnLeftSide()
+ ? draggableBounds.left
+ : draggableBounds.right, (int) mMenuView.getTranslationY());
+
+ fadeOutIfEnabled();
+ }
+
+ void moveOutEdgeAndShow() {
+ mIsMovedToEdge = false;
+
+ mMenuView.onPositionChanged();
+ mMenuView.onEdgeChangedIfNeeded();
+ }
+
+ void cancelAnimations() {
+ cancelAnimation(DynamicAnimation.TRANSLATION_X);
+ cancelAnimation(DynamicAnimation.TRANSLATION_Y);
+ }
+
+ private void cancelAnimation(DynamicAnimation.ViewProperty property) {
+ if (!mPositionAnimations.containsKey(property)) {
+ return;
+ }
+
+ mPositionAnimations.get(property).cancel();
+ }
+
+ void onDraggingStart() {
+ mMenuView.onDraggingStart();
+ }
+
+ private void onSpringAnimationEnd(PointF position) {
+ mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
+ constrainPositionAndUpdate(position);
+
+ fadeOutIfEnabled();
+ }
+
+ private void constrainPositionAndUpdate(PointF position) {
+ final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+ // Have the space gap margin between the top bound and the menu view, so actually the
+ // position y range needs to cut the margin.
+ position.offset(-draggableBounds.left, -draggableBounds.top);
+
+ final float percentageX = position.x < draggableBounds.centerX()
+ ? MIN_PERCENT : MAX_PERCENT;
+
+ final float percentageY = position.y < 0 || draggableBounds.height() == 0
+ ? MIN_PERCENT
+ : Math.min(MAX_PERCENT, position.y / draggableBounds.height());
+ mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
+ }
+
+ void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
+ mIsFadeEffectEnabled = isFadeEffectEnabled;
+
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mFadeOutAnimator.cancel();
+ mFadeOutAnimator.setFloatValues(COMPLETELY_OPAQUE, newOpacityValue);
+ mHandler.post(() -> mMenuView.setAlpha(
+ mIsFadeEffectEnabled ? newOpacityValue : COMPLETELY_OPAQUE));
+ }
+
+ void fadeInNowIfEnabled() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ cancelAndRemoveCallbacksAndMessages();
+ mHandler.post(() -> mMenuView.setAlpha(COMPLETELY_OPAQUE));
+ }
+
+ void fadeOutIfEnabled() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ cancelAndRemoveCallbacksAndMessages();
+ mHandler.postDelayed(mFadeOutAnimator::start, FADE_EFFECT_DURATION_MS);
+ }
+
+ private void cancelAndRemoveCallbacksAndMessages() {
+ mFadeOutAnimator.cancel();
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ }
+
+ private Handler createUiHandler() {
+ return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
+ }
+
+ static class MenuPositionProperty
+ extends FloatPropertyCompat<MenuView> {
+ private final DynamicAnimation.ViewProperty mProperty;
+
+ MenuPositionProperty(DynamicAnimation.ViewProperty property) {
+ super(property.toString());
+ mProperty = property;
+ }
+
+ @Override
+ public float getValue(MenuView menuView) {
+ return mProperty.getValue(menuView);
+ }
+
+ @Override
+ public void setValue(MenuView menuView, float value) {
+ mProperty.setValue(menuView, value);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt
new file mode 100644
index 000000000000..83c344cca214
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.accessibility.floatingmenu
+
+import android.annotation.FloatRange
+
+@FloatRange(from = 0.0, to = 1.0) const val DEFAULT_OPACITY_VALUE = 0.55f
+const val DEFAULT_FADE_EFFECT_IS_ENABLED = 1
+
+/** The data class for the fade effect info of the accessibility floating menu view. */
+data class MenuFadeEffectInfo(val isFadeEffectEnabled: Boolean, val opacity: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 698d60a5b13e..57019de762a4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -16,22 +16,29 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
+import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_OPACITY_VALUE;
import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL;
+import android.annotation.FloatRange;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Prefs;
import java.util.List;
@@ -39,9 +46,16 @@ import java.util.List;
* Stores and observe the settings contents for the menu view.
*/
class MenuInfoRepository {
+ @FloatRange(from = 0.0, to = 1.0)
+ private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
+
+ @FloatRange(from = 0.0, to = 1.0)
+ private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.9f;
+
private final Context mContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
+ private Position mPercentagePosition;
private final ContentObserver mMenuTargetFeaturesContentObserver =
new ContentObserver(mHandler) {
@@ -62,9 +76,24 @@ class MenuInfoRepository {
}
};
+ @VisibleForTesting
+ final ContentObserver mMenuFadeOutContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mSettingsContentsCallback.onFadeEffectInfoChanged(getMenuFadeEffectInfo());
+ }
+ };
+
MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) {
mContext = context;
mSettingsContentsCallback = settingsContentsChanged;
+
+ mPercentagePosition = getStartPosition();
+ }
+
+ void loadMenuPosition(OnInfoReady<Position> callback) {
+ callback.onReady(mPercentagePosition);
}
void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) {
@@ -75,6 +104,30 @@ class MenuInfoRepository {
callback.onReady(getMenuSizeTypeFromSettings(mContext));
}
+ void loadMenuFadeEffectInfo(OnInfoReady<MenuFadeEffectInfo> callback) {
+ callback.onReady(getMenuFadeEffectInfo());
+ }
+
+ private MenuFadeEffectInfo getMenuFadeEffectInfo() {
+ return new MenuFadeEffectInfo(isMenuFadeEffectEnabledFromSettings(mContext),
+ getMenuOpacityFromSettings(mContext));
+ }
+
+ void updateMenuSavingPosition(Position percentagePosition) {
+ mPercentagePosition = percentagePosition;
+ Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
+ percentagePosition.toString());
+ }
+
+ private Position getStartPosition() {
+ final String absolutePositionString = Prefs.getString(mContext,
+ Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
+
+ return TextUtils.isEmpty(absolutePositionString)
+ ? new Position(DEFAULT_MENU_POSITION_X_PERCENT, DEFAULT_MENU_POSITION_Y_PERCENT)
+ : Position.fromString(absolutePositionString);
+ }
+
void registerContentObservers() {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
@@ -88,17 +141,28 @@ class MenuInfoRepository {
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants */ false, mMenuSizeContentObserver,
UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
}
void unregisterContentObservers() {
mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
}
interface OnSettingsContentsChanged {
void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures);
void onSizeTypeChanged(int newSizeType);
+
+ void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo);
}
interface OnInfoReady<T> {
@@ -109,4 +173,16 @@ class MenuInfoRepository {
return Settings.Secure.getIntForUser(context.getContentResolver(),
ACCESSIBILITY_FLOATING_MENU_SIZE, SMALL, UserHandle.USER_CURRENT);
}
+
+ private static boolean isMenuFadeEffectEnabledFromSettings(Context context) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ DEFAULT_FADE_EFFECT_IS_ENABLED, UserHandle.USER_CURRENT) == /* enabled */ 1;
+ }
+
+ private static float getMenuOpacityFromSettings(Context context) {
+ return Settings.Secure.getFloatForUser(context.getContentResolver(),
+ ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY_VALUE,
+ UserHandle.USER_CURRENT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
new file mode 100644
index 000000000000..e69a24810fdc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -0,0 +1,131 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+
+import com.android.systemui.R;
+
+/**
+ * An accessibility item delegate for the individual items of the list view in the
+ * {@link MenuView}.
+ */
+class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
+ private final MenuAnimationController mAnimationController;
+
+ MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
+ MenuAnimationController animationController) {
+ super(recyclerViewDelegate);
+ mAnimationController = animationController;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ final Resources res = host.getResources();
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopLeft =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.action_move_top_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_left));
+ info.addAction(moveTopLeft);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopRight =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_top_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_right));
+ info.addAction(moveTopRight);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomLeft =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_bottom_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_left));
+ info.addAction(moveBottomLeft);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomRight =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_bottom_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_right));
+ info.addAction(moveBottomRight);
+
+ final int moveEdgeId = mAnimationController.isMovedToEdge()
+ ? R.id.action_move_out_edge_and_show
+ : R.id.action_move_to_edge_and_hide;
+ final int moveEdgeTextResId = mAnimationController.isMovedToEdge()
+ ? R.string.accessibility_floating_button_action_move_out_edge_and_show
+ : R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half;
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
+ res.getString(moveEdgeTextResId));
+ info.addAction(moveToOrOutEdge);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_ACCESSIBILITY_FOCUS) {
+ mAnimationController.fadeInNowIfEnabled();
+ }
+
+ if (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
+ mAnimationController.fadeOutIfEnabled();
+ }
+
+ if (action == R.id.action_move_top_left) {
+ mAnimationController.moveToTopLeftPosition();
+ return true;
+ }
+
+ if (action == R.id.action_move_top_right) {
+ mAnimationController.moveToTopRightPosition();
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_left) {
+ mAnimationController.moveToBottomLeftPosition();
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_right) {
+ mAnimationController.moveToBottomRightPosition();
+ return true;
+ }
+
+ if (action == R.id.action_move_to_edge_and_hide) {
+ mAnimationController.moveToEdgeAndHide();
+ return true;
+ }
+
+ if (action == R.id.action_move_out_edge_and_show) {
+ mAnimationController.moveOutEdgeAndShow();
+ return true;
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
new file mode 100644
index 000000000000..3146c9f0d2af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -0,0 +1,121 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Controls the all touch events of the accessibility target features view{@link RecyclerView} in
+ * the {@link MenuView}. And then compute the gestures' velocity for fling and spring
+ * animations.
+ */
+class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
+ private static final int VELOCITY_UNIT_SECONDS = 1000;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final MenuAnimationController mMenuAnimationController;
+ private final PointF mDown = new PointF();
+ private final PointF mMenuTranslationDown = new PointF();
+ private boolean mIsDragging = false;
+ private float mTouchSlop;
+
+ MenuListViewTouchHandler(MenuAnimationController menuAnimationController) {
+ mMenuAnimationController = menuAnimationController;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent motionEvent) {
+
+ final View menuView = (View) recyclerView.getParent();
+ addMovement(motionEvent);
+
+ final float dx = motionEvent.getRawX() - mDown.x;
+ final float dy = motionEvent.getRawY() - mDown.y;
+
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mMenuAnimationController.fadeInNowIfEnabled();
+ mTouchSlop = ViewConfiguration.get(recyclerView.getContext()).getScaledTouchSlop();
+ mDown.set(motionEvent.getRawX(), motionEvent.getRawY());
+ mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
+
+ mMenuAnimationController.cancelAnimations();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
+ if (!mIsDragging) {
+ mIsDragging = true;
+ mMenuAnimationController.onDraggingStart();
+ }
+
+ mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
+ mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsDragging) {
+ final float endX = mMenuTranslationDown.x + dx;
+ mIsDragging = false;
+
+ if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
+ mMenuAnimationController.flingMenuThenSpringToEdge(endX,
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ }
+
+ // Avoid triggering the listener of the item.
+ return true;
+ }
+
+ break;
+ default: // Do nothing
+ }
+
+ // not consume all the events here because keeping the scroll behavior of list view.
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent motionEvent) {
+ // Do nothing
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean b) {
+ // Do nothing
+ }
+
+ /**
+ * Adds a movement to the velocity tracker using raw screen coordinates.
+ */
+ private void addMovement(MotionEvent motionEvent) {
+ final float deltaX = motionEvent.getRawX() - motionEvent.getX();
+ final float deltaY = motionEvent.getRawY() - motionEvent.getY();
+ motionEvent.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(motionEvent);
+ motionEvent.offsetLocation(-deltaX, -deltaY);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 576f23ee780e..15d139cf15da 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,28 +21,44 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
- * The container view displays the accessibility features.
+ * The menu view displays the accessibility features.
*/
@SuppressLint("ViewConstructor")
-class MenuView extends FrameLayout {
+class MenuView extends FrameLayout implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
private static final int INDEX_MENU_ITEM = 0;
private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
private final AccessibilityTargetAdapter mAdapter;
private final MenuViewModel mMenuViewModel;
+ private final MenuAnimationController mMenuAnimationController;
+ private final Rect mBoundsInParent = new Rect();
private final RecyclerView mTargetFeaturesView;
+ private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater =
+ this::updateSystemGestureExcludeRects;
+ private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver =
+ this::onMenuFadeEffectInfoChanged;
+ private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition;
private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged;
private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver =
this::onTargetFeaturesChanged;
@@ -53,23 +69,47 @@ class MenuView extends FrameLayout {
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
+ mMenuAnimationController = new MenuAnimationController(this);
+
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
+ mTargetFeaturesView.setAccessibilityDelegateCompat(
+ new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
+ @NonNull
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+ mMenuAnimationController);
+ }
+ });
setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
// Avoid drawing out of bounds of the parent view
setClipToOutline(true);
+
loadLayoutResources();
addView(mTargetFeaturesView);
}
@Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ inoutInfo.touchableRegion.set(mBoundsInParent);
+ }
+
+ @Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
loadLayoutResources();
+
+ mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
+ }
+
+ void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
+ mTargetFeaturesView.addOnItemTouchListener(listener);
}
@SuppressLint("NotifyDataSetChanged")
@@ -80,11 +120,25 @@ class MenuView extends FrameLayout {
}
private void onSizeChanged() {
+ mBoundsInParent.set(mBoundsInParent.left, mBoundsInParent.top,
+ mBoundsInParent.left + mMenuViewAppearance.getMenuWidth(),
+ mBoundsInParent.top + mMenuViewAppearance.getMenuHeight());
+
final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.height = mMenuViewAppearance.getMenuHeight();
setLayoutParams(layoutParams);
}
+ void onEdgeChangedIfNeeded() {
+ final Rect draggableBounds = mMenuViewAppearance.getMenuDraggableBounds();
+ if (getTranslationX() != draggableBounds.left
+ && getTranslationX() != draggableBounds.right) {
+ return;
+ }
+
+ onEdgeChanged();
+ }
+
private void onEdgeChanged() {
final int[] insets = mMenuViewAppearance.getMenuInsets();
getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
@@ -96,8 +150,22 @@ class MenuView extends FrameLayout {
mMenuViewAppearance.getMenuStrokeColor());
}
+ private void onPercentagePosition(Position percentagePosition) {
+ mMenuViewAppearance.setPercentagePosition(percentagePosition);
+
+ onPositionChanged();
+ }
+
+ void onPositionChanged() {
+ final PointF position = mMenuViewAppearance.getMenuPosition();
+ mMenuAnimationController.moveToPosition(position);
+ onBoundsInParentChanged((int) position.x, (int) position.y);
+ }
+
@SuppressLint("NotifyDataSetChanged")
private void onSizeTypeChanged(int newSizeType) {
+ mMenuAnimationController.fadeInNowIfEnabled();
+
mMenuViewAppearance.setSizeType(newSizeType);
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
@@ -106,41 +174,117 @@ class MenuView extends FrameLayout {
onSizeChanged();
onEdgeChanged();
+ onPositionChanged();
+
+ mMenuAnimationController.fadeOutIfEnabled();
}
private void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures) {
// TODO(b/252756133): Should update specific item instead of the whole list
+ mMenuAnimationController.fadeInNowIfEnabled();
+
mTargetFeatures.clear();
mTargetFeatures.addAll(newTargetFeatures);
mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size());
+ mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
mAdapter.notifyDataSetChanged();
onSizeChanged();
onEdgeChanged();
+ onPositionChanged();
+
+ mMenuAnimationController.fadeOutIfEnabled();
+ }
+
+ private void onMenuFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) {
+ mMenuAnimationController.updateOpacityWith(fadeEffectInfo.isFadeEffectEnabled(),
+ fadeEffectInfo.getOpacity());
+ }
+
+ Rect getMenuDraggableBounds() {
+ return mMenuViewAppearance.getMenuDraggableBounds();
+ }
+
+ void persistPositionAndUpdateEdge(Position percentagePosition) {
+ mMenuViewModel.updateMenuSavingPosition(percentagePosition);
+ mMenuViewAppearance.setPercentagePosition(percentagePosition);
+
+ onEdgeChangedIfNeeded();
+ }
+
+ /**
+ * Uses the touch events from the parent view to identify if users clicked the extra
+ * space of the menu view. If yes, will use the percentage position and update the
+ * translations of the menu view to meet the effect of moving out from the edge. It’s only
+ * used when the menu view is hidden to the screen edge.
+ *
+ * @param x the current x of the touch event from the parent {@link MenuViewLayer} of the
+ * {@link MenuView}.
+ * @param y the current y of the touch event from the parent {@link MenuViewLayer} of the
+ * {@link MenuView}.
+ * @return true if consume the touch event, otherwise false.
+ */
+ boolean maybeMoveOutEdgeAndShow(int x, int y) {
+ // Utilizes the touch region of the parent view to implement that users could tap extra
+ // the space region to show the menu from the edge.
+ if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) {
+ return false;
+ }
+
+ mMenuAnimationController.fadeInNowIfEnabled();
+
+ mMenuAnimationController.moveOutEdgeAndShow();
+
+ mMenuAnimationController.fadeOutIfEnabled();
+ return true;
}
void show() {
+ mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver);
+ mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
setVisibility(VISIBLE);
mMenuViewModel.registerContentObservers();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
void hide() {
setVisibility(GONE);
+ mBoundsInParent.setEmpty();
+ mMenuViewModel.getPercentagePositionData().removeObserver(mPercentagePositionObserver);
+ mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver);
mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
mMenuViewModel.unregisterContentObservers();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
+ }
+
+ void onDraggingStart() {
+ final int[] insets = mMenuViewAppearance.getMenuMovingStateInsets();
+ getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
+ insets[3]);
+
+ final GradientDrawable gradientDrawable = getContainerViewGradient();
+ gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuMovingStateRadii());
+ }
+
+ void onBoundsInParentChanged(int newLeft, int newTop) {
+ mBoundsInParent.offsetTo(newLeft, newTop);
}
void loadLayoutResources() {
mMenuViewAppearance.update();
+ mTargetFeaturesView.setContentDescription(mMenuViewAppearance.getContentDescription());
setBackground(mMenuViewAppearance.getMenuBackground());
setElevation(mMenuViewAppearance.getMenuElevation());
onItemSizeChanged();
onSizeChanged();
onEdgeChanged();
+ onPositionChanged();
}
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
@@ -150,4 +294,9 @@ class MenuView extends FrameLayout {
private GradientDrawable getContainerViewGradient() {
return (GradientDrawable) getContainerViewInsetLayer().getDrawable(INDEX_MENU_ITEM);
}
+
+ private void updateSystemGestureExcludeRects() {
+ final ViewGroup parentView = (ViewGroup) getParent();
+ parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index b9b7732605c0..034e96a13029 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -16,12 +16,21 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.view.View.OVER_SCROLL_ALWAYS;
+import static android.view.View.OVER_SCROLL_NEVER;
+
import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.annotation.DimenRes;
@@ -34,9 +43,13 @@ import java.lang.annotation.RetentionPolicy;
* Provides the layout resources information of the {@link MenuView}.
*/
class MenuViewAppearance {
+ private final WindowManager mWindowManager;
private final Resources mRes;
+ private final Position mPercentagePosition = new Position(/* percentageX= */
+ 0f, /* percentageY= */ 0f);
private int mTargetFeaturesSize;
private int mSizeType;
+ private int mMargin;
private int mSmallPadding;
private int mLargePadding;
private int mSmallIconSize;
@@ -51,6 +64,7 @@ class MenuViewAppearance {
private int mElevation;
private float[] mRadii;
private Drawable mBackgroundDrawable;
+ private String mContentDescription;
@IntDef({
SMALL,
@@ -62,13 +76,15 @@ class MenuViewAppearance {
int LARGE = 1;
}
- MenuViewAppearance(Context context) {
+ MenuViewAppearance(Context context, WindowManager windowManager) {
+ mWindowManager = windowManager;
mRes = context.getResources();
update();
}
void update() {
+ mMargin = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
mSmallPadding =
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding);
mLargePadding =
@@ -81,7 +97,7 @@ class MenuViewAppearance {
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius);
mSmallMultipleRadius = mRes.getDimensionPixelSize(
R.dimen.accessibility_floating_menu_small_multiple_radius);
- mRadii = createRadii(getMenuRadius(mTargetFeaturesSize));
+ mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
mLargeSingleRadius =
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_single_radius);
mLargeMultipleRadius = mRes.getDimensionPixelSize(
@@ -93,18 +109,59 @@ class MenuViewAppearance {
final Drawable drawable =
mRes.getDrawable(R.drawable.accessibility_floating_menu_background);
mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable});
+ mContentDescription = mRes.getString(
+ com.android.internal.R.string.accessibility_select_shortcut_menu_title);
}
void setSizeType(int sizeType) {
mSizeType = sizeType;
- mRadii = createRadii(getMenuRadius(mTargetFeaturesSize));
+ mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
}
void setTargetFeaturesSize(int targetFeaturesSize) {
mTargetFeaturesSize = targetFeaturesSize;
- mRadii = createRadii(getMenuRadius(targetFeaturesSize));
+ mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(targetFeaturesSize));
+ }
+
+ void setPercentagePosition(Position percentagePosition) {
+ mPercentagePosition.update(percentagePosition);
+
+ mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
+ }
+
+ Rect getMenuDraggableBounds() {
+ final int margin = getMenuMargin();
+ final Rect draggableBounds = getWindowAvailableBounds();
+
+ // Initializes start position for mapping the translation of the menu view.
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+ final Insets displayCutoutInsets = windowInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.displayCutout());
+ draggableBounds.offset(-displayCutoutInsets.left, -displayCutoutInsets.top);
+
+ draggableBounds.top += margin;
+ draggableBounds.right -= getMenuWidth();
+ draggableBounds.bottom -= Math.min(
+ getWindowAvailableBounds().height() - draggableBounds.top,
+ calculateActualMenuHeight() + margin);
+ return draggableBounds;
+ }
+
+ PointF getMenuPosition() {
+ final Rect draggableBounds = getMenuDraggableBounds();
+
+ return new PointF(
+ draggableBounds.left
+ + draggableBounds.width() * mPercentagePosition.getPercentageX(),
+ draggableBounds.top
+ + draggableBounds.height() * mPercentagePosition.getPercentageY());
+ }
+
+ String getContentDescription() {
+ return mContentDescription;
}
Drawable getMenuBackground() {
@@ -115,20 +172,41 @@ class MenuViewAppearance {
return mElevation;
}
+ int getMenuWidth() {
+ return getMenuPadding() * 2 + getMenuIconSize();
+ }
+
int getMenuHeight() {
- return calculateActualMenuHeight();
+ return Math.min(getWindowAvailableBounds().height() - mMargin * 2,
+ calculateActualMenuHeight());
}
int getMenuIconSize() {
return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize;
}
+ private int getMenuMargin() {
+ return mMargin;
+ }
+
int getMenuPadding() {
return mSizeType == SMALL ? mSmallPadding : mLargePadding;
}
int[] getMenuInsets() {
- return new int[]{mInset, 0, 0, 0};
+ final int left = isMenuOnLeftSide() ? mInset : 0;
+ final int right = isMenuOnLeftSide() ? 0 : mInset;
+
+ return new int[]{left, 0, right, 0};
+ }
+
+ int[] getMenuMovingStateInsets() {
+ return new int[]{0, 0, 0, 0};
+ }
+
+ float[] getMenuMovingStateRadii() {
+ final float radius = getMenuRadius(mTargetFeaturesSize);
+ return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
}
int getMenuStrokeWidth() {
@@ -147,6 +225,14 @@ class MenuViewAppearance {
return mSizeType == SMALL ? getSmallSize(itemCount) : getLargeSize(itemCount);
}
+ int getMenuScrollMode() {
+ return hasExceededMaxWindowHeight() ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER;
+ }
+
+ private boolean hasExceededMaxWindowHeight() {
+ return calculateActualMenuHeight() > getWindowAvailableBounds().height();
+ }
+
@DimenRes
private int getSmallSize(int itemCount) {
return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius;
@@ -157,8 +243,29 @@ class MenuViewAppearance {
return itemCount > 1 ? mLargeMultipleRadius : mLargeSingleRadius;
}
- private static float[] createRadii(float radius) {
- return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f};
+ private static float[] createRadii(boolean isMenuOnLeftSide, float radius) {
+ return isMenuOnLeftSide
+ ? new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f}
+ : new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ }
+
+ private Rect getWindowAvailableBounds() {
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+
+ final Rect bounds = new Rect(windowMetrics.getBounds());
+ bounds.left += insets.left;
+ bounds.right -= insets.right;
+ bounds.top += insets.top;
+ bounds.bottom -= insets.bottom;
+
+ return bounds;
+ }
+
+ private boolean isMenuOnLeftSide() {
+ return mPercentagePosition.getPercentageX() < 0.5f;
}
private int calculateActualMenuHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 4ea2f7799c30..5252519e9faf 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -19,6 +19,8 @@ package com.android.systemui.accessibility.floatingmenu;
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.view.MotionEvent;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -41,17 +43,27 @@ class MenuViewLayer extends FrameLayout {
int MENU_VIEW = 0;
}
- MenuViewLayer(@NonNull Context context) {
+ MenuViewLayer(@NonNull Context context, WindowManager windowManager) {
super(context);
final MenuViewModel menuViewModel = new MenuViewModel(context);
- final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context);
+ final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
+ windowManager);
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
addView(mMenuView, LayerIndex.MENU_VIEW);
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mMenuView.maybeMoveOutEdgeAndShow((int) event.getX(), (int) event.getY())) {
+ return true;
+ }
+
+ return super.onInterceptTouchEvent(event);
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 1e15a599f796..d2093c200ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_
import android.content.Context;
import android.graphics.PixelFormat;
+import android.view.WindowInsets;
import android.view.WindowManager;
/**
@@ -33,7 +34,7 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
MenuViewLayerController(Context context, WindowManager windowManager) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context);
+ mMenuViewLayer = new MenuViewLayer(context, windowManager);
}
@Override
@@ -68,9 +69,10 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
+ params.receiveInsetsIgnoringZOrder = true;
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
-
+ params.setFitInsetsTypes(WindowInsets.Type.navigationBars());
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index c3ba43950b6e..e8a2b6e8767b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -33,6 +33,9 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
private final MutableLiveData<List<AccessibilityTarget>> mTargetFeaturesData =
new MutableLiveData<>();
private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>();
+ private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
+ new MutableLiveData<>();
+ private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
MenuViewModel(Context context) {
@@ -49,11 +52,30 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
mSizeTypeData.setValue(newSizeType);
}
+ @Override
+ public void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) {
+ mFadeEffectInfoData.setValue(fadeEffectInfo);
+ }
+
+ void updateMenuSavingPosition(Position percentagePosition) {
+ mInfoRepository.updateMenuSavingPosition(percentagePosition);
+ }
+
+ LiveData<Position> getPercentagePositionData() {
+ mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
+ return mPercentagePositionData;
+ }
+
LiveData<Integer> getSizeTypeData() {
mInfoRepository.loadMenuSizeType(mSizeTypeData::setValue);
return mSizeTypeData;
}
+ LiveData<MenuFadeEffectInfo> getFadeEffectInfoData() {
+ mInfoRepository.loadMenuFadeEffectInfo(mFadeEffectInfoData::setValue);
+ return mFadeEffectInfoData;
+ }
+
LiveData<List<AccessibilityTarget>> getTargetFeaturesData() {
mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue);
return mTargetFeaturesData;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java
index 7b7eda84df4c..fc21be280dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.text.TextUtils;
/**
@@ -62,6 +63,13 @@ public class Position {
}
/**
+ * Updates the position with {@code percentagePosition}.
+ */
+ public void update(@NonNull Position percentagePosition) {
+ update(percentagePosition.getPercentageX(), percentagePosition.getPercentageY());
+ }
+
+ /**
* Updates the position with {@code percentageX} and {@code percentageY}.
*
* @param percentageX the new percentage of X-axis of the screen, from 0.0 to 1.0.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 9493975ca00f..8c7e0efee7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -652,17 +652,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
}
- /**
- * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
- * sensor area even if the user hasn't explicitly lifted their finger yet.
- */
- public void onCancelUdfps() {
- if (mUdfpsController == null) {
- return;
- }
- mUdfpsController.onCancelUdfps();
- }
-
private void sendResultAndCleanUp(@DismissedReason int reason,
@Nullable byte[] credentialAttestation) {
if (mReceiver == null) {
@@ -1021,8 +1010,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
} else {
Log.w(TAG, "onBiometricError callback but dialog is gone");
}
-
- onCancelUdfps();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 5ed898682883..76cd3f4c4f1d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.graphics.Insets;
import android.os.UserHandle;
import android.text.InputType;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
@@ -151,39 +152,52 @@ public class AuthCredentialPasswordView extends AuthCredentialView
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (mAuthCredentialInput == null || mAuthCredentialHeader == null
- || mSubtitleView == null || mPasswordField == null || mErrorView == null) {
+ if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
+ || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
return;
}
- // b/157910732 In AuthContainerView#getLayoutParams() we used to prevent jank risk when
- // resizing by IME show or hide, we used to setFitInsetsTypes `~WindowInsets.Type.ime()` to
- // LP. As a result this view needs to listen onApplyWindowInsets() and handle onLayout.
int inputLeftBound;
int inputTopBound;
int headerRightBound = right;
+ int headerTopBounds = top;
+ final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
+ : mSubtitleView.getBottom();
+ final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
+ : mDescriptionView.getBottom();
if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- inputTopBound = (bottom - (mPasswordField.getHeight() + mErrorView.getHeight())) / 2;
+ inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
inputLeftBound = (right - left) / 2;
headerRightBound = inputLeftBound;
+ headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
} else {
- inputTopBound = mSubtitleView.getBottom() + (bottom - mSubtitleView.getBottom()) / 2;
+ inputTopBound =
+ descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
}
- mAuthCredentialHeader.layout(left, top, headerRightBound, bottom);
+ if (mDescriptionView.getBottom() > mBottomInset) {
+ mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
+ }
mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), newHeight);
+ setMeasuredDimension(newWidth, newHeight);
- measureChildren(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.AT_MOST));
+ final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
+ MeasureSpec.AT_MOST);
+ final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
+ if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ measureChildren(halfWidthSpec, fullHeightSpec);
+ } else {
+ measureChildren(widthMeasureSpec, fullHeightSpec);
+ }
}
@NonNull
@@ -193,6 +207,20 @@ public class AuthCredentialPasswordView extends AuthCredentialView
final Insets bottomInset = insets.getInsets(ime());
if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
mBottomInset = bottomInset.bottom;
+ if (mBottomInset > 0
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ mTitleView.setSingleLine(true);
+ mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ mTitleView.setMarqueeRepeatLimit(-1);
+ // select to enable marquee unless a screen reader is enabled
+ mTitleView.setSelected(!mAccessibilityManager.isEnabled()
+ || !mAccessibilityManager.isTouchExplorationEnabled());
+ } else {
+ mTitleView.setSingleLine(false);
+ mTitleView.setEllipsize(null);
+ // select to enable marquee unless a screen reader is enabled
+ mTitleView.setSelected(false);
+ }
requestLayout();
}
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 11498dbc0b83..f9e44a0c1724 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -93,7 +93,9 @@ public class AuthCredentialPatternView extends AuthCredentialView {
@Override
protected void onErrorTimeoutFinish() {
super.onErrorTimeoutFinish();
- mLockPatternView.setEnabled(true);
+ // select to enable marquee unless a screen reader is enabled
+ mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
+ || !mAccessibilityManager.isTouchExplorationEnabled());
}
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index d4176acf10f9..fa623d146756 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -77,7 +77,7 @@ public abstract class AuthCredentialView extends LinearLayout {
protected final Handler mHandler;
protected final LockPatternUtils mLockPatternUtils;
- private final AccessibilityManager mAccessibilityManager;
+ protected final AccessibilityManager mAccessibilityManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
@@ -86,10 +86,10 @@ public abstract class AuthCredentialView extends LinearLayout {
private boolean mShouldAnimatePanel;
private boolean mShouldAnimateContents;
- private TextView mTitleView;
+ protected TextView mTitleView;
protected TextView mSubtitleView;
- private TextView mDescriptionView;
- private ImageView mIconView;
+ protected TextView mDescriptionView;
+ protected ImageView mIconView;
protected TextView mErrorView;
protected @Utils.CredentialType int mCredentialType;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 0f5a99c1596d..3273d7429d49 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -788,7 +788,7 @@ public class UdfpsController implements DozeReceiver {
// ACTION_UP/ACTION_CANCEL, we need to be careful about not letting the screen
// accidentally remain in high brightness mode. As a mitigation, queue a call to
// cancel the fingerprint scan.
- mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps,
+ mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::cancelAodInterrupt,
AOD_INTERRUPT_TIMEOUT_MILLIS);
// using a hard-coded value for major and minor until it is available from the sensor
onFingerDown(requestId, screenX, screenY, minor, major);
@@ -815,26 +815,22 @@ public class UdfpsController implements DozeReceiver {
}
/**
- * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly
- * lifts their finger. Generally, this should be called on errors in the authentication flow.
- *
- * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
- * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
+ * The sensor that triggers {@link #onAodInterrupt} doesn't emit ACTION_UP or ACTION_CANCEL
+ * events, which means the fingerprint gesture created by the AOD interrupt needs to be
+ * cancelled manually.
* This should be called when authentication either succeeds or fails. Failing to cancel the
* scan will leave the display in the UDFPS mode until the user lifts their finger. On optical
* sensors, this can result in illumination persisting for longer than necessary.
*/
- void onCancelUdfps() {
+ @VisibleForTesting
+ void cancelAodInterrupt() {
if (!mIsAodInterruptActive) {
return;
}
if (mOverlay != null && mOverlay.getOverlayView() != null) {
onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
}
- if (mCancelAodTimeoutAction != null) {
- mCancelAodTimeoutAction.run();
- mCancelAodTimeoutAction = null;
- }
+ mCancelAodTimeoutAction = null;
mIsAodInterruptActive = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 66a521c30f47..7d0109686351 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -21,13 +21,18 @@ import android.annotation.UiThread
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.os.Build
import android.os.RemoteException
+import android.provider.Settings
import android.util.Log
import android.util.RotationUtils
import android.view.LayoutInflater
@@ -38,6 +43,7 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.LayoutRes
+import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -54,13 +60,16 @@ import com.android.systemui.util.time.SystemClock
private const val TAG = "UdfpsControllerOverlay"
+@VisibleForTesting
+const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
+
/**
* Keeps track of the overlay state and UI resources associated with a single FingerprintService
* request. This state can persist across configuration changes via the [show] and [hide]
* methods.
*/
@UiThread
-class UdfpsControllerOverlay(
+class UdfpsControllerOverlay @JvmOverloads constructor(
private val context: Context,
fingerprintManager: FingerprintManager,
private val inflater: LayoutInflater,
@@ -82,7 +91,8 @@ class UdfpsControllerOverlay(
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -102,18 +112,19 @@ class UdfpsControllerOverlay(
gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
}
/** A helper if the [requestReason] was due to enrollment. */
- val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) {
- UdfpsEnrollHelper(context, fingerprintManager, requestReason)
- } else {
- null
- }
+ val enrollHelper: UdfpsEnrollHelper? =
+ if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
+ UdfpsEnrollHelper(context, fingerprintManager, requestReason)
+ } else {
+ null
+ }
/** If the overlay is currently showing. */
val isShowing: Boolean
@@ -129,6 +140,17 @@ class UdfpsControllerOverlay(
private var touchExplorationEnabled = false
+ private fun shouldRemoveEnrollmentUi(): Boolean {
+ if (isDebuggable) {
+ return Settings.Global.getInt(
+ context.contentResolver,
+ SETTING_REMOVE_ENROLLMENT_UI,
+ 0 /* def */
+ ) != 0
+ }
+ return false
+ }
+
/** Show the overlay or return false and do nothing if it is already showing. */
@SuppressLint("ClickableViewAccessibility")
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
@@ -183,7 +205,18 @@ class UdfpsControllerOverlay(
view: UdfpsView,
controller: UdfpsController
): UdfpsAnimationViewController<*>? {
- return when (requestReason) {
+ val isEnrollment = when (requestReason) {
+ REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+ else -> false
+ }
+
+ val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) {
+ REASON_AUTH_OTHER
+ } else {
+ requestReason
+ }
+
+ return when (filteredRequestReason) {
REASON_ENROLL_FIND_SENSOR,
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
@@ -198,7 +231,7 @@ class UdfpsControllerOverlay(
overlayParams.scaleFactor
)
}
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> {
+ REASON_AUTH_KEYGUARD -> {
UdfpsKeyguardViewController(
view.addUdfpsView(R.layout.udfps_keyguard_view),
statusBarStateController,
@@ -216,7 +249,7 @@ class UdfpsControllerOverlay(
activityLaunchAnimator
)
}
- BiometricOverlayConstants.REASON_AUTH_BP -> {
+ REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
@@ -226,8 +259,8 @@ class UdfpsControllerOverlay(
dumpManager
)
}
- BiometricOverlayConstants.REASON_AUTH_OTHER,
- BiometricOverlayConstants.REASON_AUTH_SETTINGS -> {
+ REASON_AUTH_OTHER,
+ REASON_AUTH_SETTINGS -> {
UdfpsFpmOtherViewController(
view.addUdfpsView(R.layout.udfps_fpm_other_view),
statusBarStateController,
@@ -440,4 +473,4 @@ private fun Int.isEnrollmentReason() =
private fun Int.isImportantForAccessibility() =
this == REASON_ENROLL_FIND_SENSOR ||
this == REASON_ENROLL_ENROLLING ||
- this == BiometricOverlayConstants.REASON_AUTH_BP
+ this == REASON_AUTH_BP
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 3871248eccd5..858bac30880b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -44,9 +44,6 @@ public interface FalsingCollector {
void onQsDown();
/** */
- void setQsExpanded(boolean expanded);
-
- /** */
boolean shouldEnforceBouncer();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 28aac051c66d..0b7d6ab5acf7 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -49,10 +49,6 @@ public class FalsingCollectorFake implements FalsingCollector {
}
@Override
- public void setQsExpanded(boolean expanded) {
- }
-
- @Override
public boolean shouldEnforceBouncer() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index f5f9655ef24b..da3d293d543b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -23,6 +23,8 @@ import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
import android.view.MotionEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.SysUISingleton;
@@ -30,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -133,6 +136,7 @@ class FalsingCollectorImpl implements FalsingCollector {
ProximitySensor proximitySensor,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
+ ShadeExpansionStateManager shadeExpansionStateManager,
BatteryController batteryController,
DockManager dockManager,
@Main DelayableExecutor mainExecutor,
@@ -157,6 +161,8 @@ class FalsingCollectorImpl implements FalsingCollector {
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+
mBatteryController.addCallback(mBatteryListener);
mDockManager.addListener(mDockEventListener);
}
@@ -193,8 +199,8 @@ class FalsingCollectorImpl implements FalsingCollector {
public void onQsDown() {
}
- @Override
- public void setQsExpanded(boolean expanded) {
+ @VisibleForTesting
+ void onQsExpansionChanged(Boolean expanded) {
if (expanded) {
unregisterSensors();
} else if (mSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
index bebade0cc484..08e8293cbe9c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.shared.model
import android.annotation.StringRes
+import android.content.Context
/**
* Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
@@ -30,4 +31,20 @@ sealed class ContentDescription {
data class Resource(
@StringRes val res: Int,
) : ContentDescription()
+
+ companion object {
+ /**
+ * Returns the loaded content description string, or null if we don't have one.
+ *
+ * Prefer [com.android.systemui.common.ui.binder.ContentDescriptionViewBinder.bind] over
+ * this method. This should only be used for testing or concatenation purposes.
+ */
+ fun ContentDescription?.loadContentDescription(context: Context): String? {
+ return when (this) {
+ null -> null
+ is Loaded -> this.description
+ is Resource -> context.getString(this.res)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
index 5d0e08ffc307..4a5693202dba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
@@ -18,6 +18,7 @@
package com.android.systemui.common.shared.model
import android.annotation.StringRes
+import android.content.Context
/**
* Models a text, that can either be already [loaded][Text.Loaded] or be a [reference]
@@ -31,4 +32,20 @@ sealed class Text {
data class Resource(
@StringRes val res: Int,
) : Text()
+
+ companion object {
+ /**
+ * Returns the loaded test string, or null if we don't have one.
+ *
+ * Prefer [com.android.systemui.common.ui.binder.TextViewBinder.bind] over this method. This
+ * should only be used for testing or concatenation purposes.
+ */
+ fun Text?.loadText(context: Context): String? {
+ return when (this) {
+ null -> null
+ is Loaded -> this.text
+ is Resource -> context.getString(this.res)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index d7638d663dc9..7e31626983e7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,8 @@ import android.service.dreams.IDreamManager;
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockModule;
+import com.android.keyguard.clock.ClockInfoModule;
+import com.android.keyguard.dagger.ClockRegistryModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
@@ -120,7 +121,8 @@ import dagger.Provides;
BiometricsModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
- ClockModule.class,
+ ClockInfoModule.class,
+ ClockRegistryModule.class,
CoroutinesModule.class,
DreamModule.class,
ControlsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 5fdd198c19d7..c256e447056b 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -99,7 +99,7 @@ class FaceScanningProviderFactory @Inject constructor(
}
fun shouldShowFaceScanningAnim(): Boolean {
- return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning
+ return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2e51b51d2836..b69afeb37371 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -287,8 +287,8 @@ public class DozeLog implements Dumpable {
/**
* Appends sensor event dropped event to logs
*/
- public void traceSensorEventDropped(int sensorEvent, String reason) {
- mLogger.logSensorEventDropped(sensorEvent, reason);
+ public void traceSensorEventDropped(@Reason int pulseReason, String reason) {
+ mLogger.logSensorEventDropped(pulseReason, reason);
}
/**
@@ -386,6 +386,47 @@ public class DozeLog implements Dumpable {
mLogger.logSetAodDimmingScrim((long) scrimOpacity);
}
+ /**
+ * Appends sensor attempted to register and whether it was a successful registration.
+ */
+ public void traceSensorRegisterAttempt(String sensorName, boolean successfulRegistration) {
+ mLogger.logSensorRegisterAttempt(sensorName, successfulRegistration);
+ }
+
+ /**
+ * Appends sensor attempted to unregister and whether it was successfully unregistered.
+ */
+ public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered) {
+ mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered);
+ }
+
+ /**
+ * Appends sensor attempted to unregister and whether it was successfully unregistered
+ * with a reason the sensor is being unregistered.
+ */
+ public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered,
+ String reason) {
+ mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered, reason);
+ }
+
+ /**
+ * Appends the event of skipping a sensor registration since it's already registered.
+ */
+ public void traceSkipRegisterSensor(String sensorInfo) {
+ mLogger.logSkipSensorRegistration(sensorInfo);
+ }
+
+ /**
+ * Appends a plugin sensor was registered or unregistered event.
+ */
+ public void tracePluginSensorUpdate(boolean registered) {
+ if (registered) {
+ mLogger.log("register plugin sensor");
+ } else {
+ mLogger.log("unregister plugin sensor");
+ }
+ }
+
private class SummaryStats {
private int mCount;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 0e1bfba8aadb..18c8e01cbf76 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -25,6 +25,7 @@ import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.log.dagger.DozeLog
import com.android.systemui.statusbar.policy.DevicePostureController
+import com.google.errorprone.annotations.CompileTimeConstant
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -224,10 +225,14 @@ class DozeLogger @Inject constructor(
})
}
- fun logPulseDropped(from: String, state: DozeMachine.State) {
+ /**
+ * Log why a pulse was dropped and the current doze machine state. The state can be null
+ * if the DozeMachine is the middle of transitioning between states.
+ */
+ fun logPulseDropped(from: String, state: DozeMachine.State?) {
buffer.log(TAG, INFO, {
str1 = from
- str2 = state.name
+ str2 = state?.name
}, {
"Pulse dropped, cannot pulse from=$str1 state=$str2"
})
@@ -320,6 +325,50 @@ class DozeLogger @Inject constructor(
"Doze car mode started"
})
}
+
+ fun logSensorRegisterAttempt(sensorInfo: String, successfulRegistration: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulRegistration
+ }, {
+ "Register sensor. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSensorUnregisterAttempt(sensorInfo: String, successfulUnregister: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulUnregister
+ }, {
+ "Unregister sensor. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSensorUnregisterAttempt(
+ sensorInfo: String,
+ successfulUnregister: Boolean,
+ reason: String
+ ) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulUnregister
+ str2 = reason
+ }, {
+ "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSkipSensorRegistration(sensor: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = sensor
+ }, {
+ "Skipping sensor registration because its already registered. sensor=$str1"
+ })
+ }
+
+ fun log(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, DEBUG, msg)
+ }
}
private const val TAG = "DozeLog"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 997a6e554364..d0258d37cc96 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -25,7 +25,6 @@ import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_
import android.annotation.AnyThread;
import android.app.ActivityManager;
-import android.content.Context;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -40,7 +39,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
-import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -91,12 +89,9 @@ import java.util.function.Consumer;
* trigger callbacks on the provided {@link mProxCallback}.
*/
public class DozeSensors {
-
- private static final boolean DEBUG = DozeService.DEBUG;
private static final String TAG = "DozeSensors";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
- private final Context mContext;
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
private final WakeLock mWakeLock;
@@ -147,7 +142,6 @@ public class DozeSensors {
}
DozeSensors(
- Context context,
AsyncSensorManager sensorManager,
DozeParameters dozeParameters,
AmbientDisplayConfiguration config,
@@ -160,7 +154,6 @@ public class DozeSensors {
AuthController authController,
DevicePostureController devicePostureController
) {
- mContext = context;
mSensorManager = sensorManager;
mConfig = config;
mWakeLock = wakeLock;
@@ -608,10 +601,7 @@ public class DozeSensors {
// cancel the previous sensor:
if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
- if (DEBUG) {
- Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
- + rt);
- }
+ mDozeLog.traceSensorUnregisterAttempt(oldSensor.toString(), rt, "posture changed");
mRegistered = false;
}
@@ -657,19 +647,13 @@ public class DozeSensors {
if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
if (!mRegistered) {
mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
- if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
- }
+ mDozeLog.traceSensorRegisterAttempt(sensor.toString(), mRegistered);
} else {
- if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
- }
+ mDozeLog.traceSkipRegisterSensor(sensor.toString());
}
} else if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
- if (DEBUG) {
- Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
- }
+ mDozeLog.traceSensorUnregisterAttempt(sensor.toString(), rt);
mRegistered = false;
}
}
@@ -707,7 +691,6 @@ public class DozeSensors {
final Sensor sensor = mSensors[mPosture];
mDozeLog.traceSensor(mPulseReason);
mHandler.post(mWakeLock.wrap(() -> {
- if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
}
@@ -779,11 +762,11 @@ public class DozeSensors {
&& !mRegistered) {
asyncSensorManager.registerPluginListener(mPluginSensor, this);
mRegistered = true;
- if (DEBUG) Log.d(TAG, "registerPluginListener");
+ mDozeLog.tracePluginSensorUpdate(true /* registered */);
} else if (mRegistered) {
asyncSensorManager.unregisterPluginListener(mPluginSensor, this);
mRegistered = false;
- if (DEBUG) Log.d(TAG, "unregisterPluginListener");
+ mDozeLog.tracePluginSensorUpdate(false /* registered */);
}
}
@@ -816,10 +799,9 @@ public class DozeSensors {
mHandler.post(mWakeLock.wrap(() -> {
final long now = SystemClock.uptimeMillis();
if (now < mDebounceFrom + mDebounce) {
- Log.d(TAG, "onSensorEvent dropped: " + triggerEventToString(event));
+ mDozeLog.traceSensorEventDropped(mPulseReason, "debounce");
return;
}
- if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
}));
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index ef454ffbdeb1..32cb1c01b776 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -198,7 +198,7 @@ public class DozeTriggers implements DozeMachine.Part {
mAllowPulseTriggers = true;
mSessionTracker = sessionTracker;
- mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
+ mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
secureSettings, authController, devicePostureController);
mDockManager = dockManager;
@@ -536,13 +536,13 @@ public class DozeTriggers implements DozeMachine.Part {
return;
}
- if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+ if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse(dozeState)) {
if (!mAllowPulseTriggers) {
mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
} else if (mDozeHost.isPulsePending()) {
mDozeLog.tracePulseDropped("requestPulse - pulsePending");
- } else if (!canPulse()) {
- mDozeLog.tracePulseDropped("requestPulse", dozeState);
+ } else if (!canPulse(dozeState)) {
+ mDozeLog.tracePulseDropped("requestPulse - dozeState cannot pulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
@@ -559,15 +559,16 @@ public class DozeTriggers implements DozeMachine.Part {
// not in pocket, continue pulsing
final boolean isPulsePending = mDozeHost.isPulsePending();
mDozeHost.setPulsePending(false);
- if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+ if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse(dozeState)) {
if (!isPulsePending) {
mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+ " pending, pulse was cancelled before it could start"
+ " transitioning to pulsing state.");
} else if (mDozeHost.isPulsingBlocked()) {
mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
- } else if (!canPulse()) {
- mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+ } else if (!canPulse(dozeState)) {
+ mDozeLog.tracePulseDropped("continuePulseRequest"
+ + " - doze state cannot pulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
@@ -582,10 +583,10 @@ public class DozeTriggers implements DozeMachine.Part {
.ifPresent(uiEventEnum -> mUiEventLogger.log(uiEventEnum, getKeyguardSessionId()));
}
- private boolean canPulse() {
- return mMachine.getState() == DozeMachine.State.DOZE
- || mMachine.getState() == DozeMachine.State.DOZE_AOD
- || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
+ private boolean canPulse(DozeMachine.State dozeState) {
+ return dozeState == DozeMachine.State.DOZE
+ || dozeState == DozeMachine.State.DOZE_AOD
+ || dozeState == DozeMachine.State.DOZE_AOD_DOCKED;
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 478f86169718..609bd76cf210 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,8 +24,13 @@ import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.dump.nano.SystemUIProtoDump
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.google.protobuf.nano.MessageNano
+import java.io.BufferedOutputStream
+import java.io.FileDescriptor
+import java.io.FileOutputStream
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -100,7 +105,7 @@ class DumpHandler @Inject constructor(
/**
* Dump the diagnostics! Behavior can be controlled via [args].
*/
- fun dump(pw: PrintWriter, args: Array<String>) {
+ fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
Trace.beginSection("DumpManager#dump()")
val start = SystemClock.uptimeMillis()
@@ -111,10 +116,12 @@ class DumpHandler @Inject constructor(
return
}
- when (parsedArgs.dumpPriority) {
- PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
- PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs)
- else -> dumpParameterized(pw, parsedArgs)
+ when {
+ parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
+ parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> {
+ dumpNormal(pw, parsedArgs)
+ }
+ else -> dumpParameterized(fd, pw, parsedArgs)
}
pw.println()
@@ -122,7 +129,7 @@ class DumpHandler @Inject constructor(
Trace.endSection()
}
- private fun dumpParameterized(pw: PrintWriter, args: ParsedArgs) {
+ private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
when (args.command) {
"bugreport-critical" -> dumpCritical(pw, args)
"bugreport-normal" -> dumpNormal(pw, args)
@@ -130,7 +137,13 @@ class DumpHandler @Inject constructor(
"buffers" -> dumpBuffers(pw, args)
"config" -> dumpConfig(pw)
"help" -> dumpHelp(pw)
- else -> dumpTargets(args.nonFlagArgs, pw, args)
+ else -> {
+ if (args.proto) {
+ dumpProtoTargets(args.nonFlagArgs, fd, args)
+ } else {
+ dumpTargets(args.nonFlagArgs, pw, args)
+ }
+ }
}
}
@@ -160,6 +173,26 @@ class DumpHandler @Inject constructor(
}
}
+ private fun dumpProtoTargets(
+ targets: List<String>,
+ fd: FileDescriptor,
+ args: ParsedArgs
+ ) {
+ val systemUIProto = SystemUIProtoDump()
+ if (targets.isNotEmpty()) {
+ for (target in targets) {
+ dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+ }
+ } else {
+ dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+ }
+ val buffer = BufferedOutputStream(FileOutputStream(fd))
+ buffer.use {
+ it.write(MessageNano.toByteArray(systemUIProto))
+ it.flush()
+ }
+ }
+
private fun dumpTargets(
targets: List<String>,
pw: PrintWriter,
@@ -267,6 +300,7 @@ class DumpHandler @Inject constructor(
}
}
}
+ PROTO -> pArgs.proto = true
"-t", "--tail" -> {
pArgs.tailLength = readArgument(iterator, arg) {
it.toInt()
@@ -278,6 +312,9 @@ class DumpHandler @Inject constructor(
"-h", "--help" -> {
pArgs.command = "help"
}
+ // This flag is passed as part of the proto dump in Bug reports, we can ignore
+ // it because this is our default behavior.
+ "-a" -> {}
else -> {
throw ArgParseException("Unknown flag: $arg")
}
@@ -314,7 +351,7 @@ class DumpHandler @Inject constructor(
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
const val PRIORITY_ARG_HIGH = "HIGH"
const val PRIORITY_ARG_NORMAL = "NORMAL"
- const val PROTO = "--sysui_proto"
+ const val PROTO = "--proto"
}
}
@@ -338,6 +375,7 @@ private class ParsedArgs(
var tailLength: Int = 0
var command: String? = null
var listOnly = false
+ var proto = false
}
class ArgParseException(message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index dbca65122fcb..ae780896a7e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,6 +18,8 @@ package com.android.systemui.dump
import android.util.ArrayMap
import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
+import com.android.systemui.dump.nano.SystemUIProtoDump
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import javax.inject.Inject
@@ -90,7 +92,7 @@ open class DumpManager @Inject constructor() {
target: String,
pw: PrintWriter,
args: Array<String>,
- tailLength: Int
+ tailLength: Int,
) {
for (dumpable in dumpables.values) {
if (dumpable.name.endsWith(target)) {
@@ -107,6 +109,36 @@ open class DumpManager @Inject constructor() {
}
}
+ @Synchronized
+ fun dumpProtoTarget(
+ target: String,
+ protoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) {
+ dumpProtoDumpable(dumpable.dumpable, protoDump, args)
+ return
+ }
+ }
+ }
+
+ @Synchronized
+ fun dumpProtoDumpables(
+ systemUIProtoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.dumpable is ProtoDumpable) {
+ dumpProtoDumpable(
+ dumpable.dumpable,
+ systemUIProtoDump,
+ args
+ )
+ }
+ }
+ }
+
/**
* Dumps all registered dumpables to [pw]
*/
@@ -184,6 +216,14 @@ open class DumpManager @Inject constructor() {
buffer.dumpable.dump(pw, tailLength)
}
+ private fun dumpProtoDumpable(
+ protoDumpable: ProtoDumpable,
+ systemUIProtoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ protoDumpable.dumpProto(systemUIProtoDump, args)
+ }
+
private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
return existingDumpable == null || newDumpable == existingDumpable
@@ -195,4 +235,4 @@ private data class RegisteredDumpable<T>(
val dumpable: T
)
-private const val TAG = "DumpManager" \ No newline at end of file
+private const val TAG = "DumpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
index 0a41a56b5ecb..da983ab03a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
+++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
@@ -51,6 +51,7 @@ public class SystemUIAuxiliaryDumpService extends Service {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// Simulate the NORMAL priority arg being passed to us
mDumpHandler.dump(
+ fd,
pw,
new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL });
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/sysui.proto b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
new file mode 100644
index 000000000000..cd8c08aeb2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+syntax = "proto3";
+
+package com.android.systemui.dump;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto";
+
+option java_multiple_files = true;
+
+message SystemUIProtoDump {
+ repeated com.android.systemui.qs.QsTileState tiles = 1;
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
deleted file mode 100644
index 906153704076..000000000000
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2021 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.flags;
-
-import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
-
-import com.android.internal.annotations.Keep;
-import com.android.systemui.R;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * List of {@link Flag} objects for use in SystemUI.
- *
- * Flag Ids are integers.
- * Ids must be unique. This is enforced in a unit test.
- * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related features with
- * a comment. This is purely for organizational purposes.
- *
- * On public release builds, flags will always return their default value. There is no way to
- * change their value on release builds.
- *
- * See {@link FeatureFlagsDebug} for instructions on flipping the flags via adb.
- */
-public class Flags {
- public static final UnreleasedFlag TEAMFOOD = new UnreleasedFlag(1);
-
- /***************************************/
- // 100 - notification
- public static final UnreleasedFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
- new UnreleasedFlag(103);
-
- public static final UnreleasedFlag NSSL_DEBUG_LINES =
- new UnreleasedFlag(105);
-
- public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION =
- new UnreleasedFlag(106);
-
- public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
- new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
-
- public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
- new ReleasedFlag(109);
-
- public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD =
- new UnreleasedFlag(110, true);
-
- public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
-
- public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
- false);
-
- public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
-
- public static final UnreleasedFlag STABILITY_INDEX_FIX = new UnreleasedFlag(114, true);
-
- public static final UnreleasedFlag SEMI_STABLE_SORT = new UnreleasedFlag(115, true);
-
- // next id: 116
-
- /***************************************/
- // 200 - keyguard/lockscreen
-
- // ** Flag retired **
- // public static final BooleanFlag KEYGUARD_LAYOUT =
- // new BooleanFlag(200, true);
-
- public static final ReleasedFlag LOCKSCREEN_ANIMATIONS =
- new ReleasedFlag(201);
-
- public static final ReleasedFlag NEW_UNLOCK_SWIPE_ANIMATION =
- new ReleasedFlag(202);
-
- public static final ResourceBooleanFlag CHARGING_RIPPLE =
- new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
-
- public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
- new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
-
- public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
- new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
-
- public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
-
- /**
- * Flag to enable the usage of the new bouncer data source. This is a refactor of and
- * eventual replacement of KeyguardBouncer.java.
- */
- public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
-
- /**
- * Whether the user interactor and repository should use `UserSwitcherController`.
- *
- * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
- * the framework APIs.
- */
- public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- new UnreleasedFlag(210);
-
- /**
- * Whether `UserSwitcherController` should use the user interactor.
- *
- * <p>When this is {@code true}, the controller does not directly access framework APIs.
- * Instead, it goes through the interactor.
- *
- * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
- * {@code true} as it would created a cycle between controller -> interactor -> controller.
- */
- public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
-
- /**
- * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
- * the digits when the clock moves.
- */
- public static final UnreleasedFlag STEP_CLOCK_ANIMATION = new UnreleasedFlag(212);
-
- /***************************************/
- // 300 - power menu
- public static final ReleasedFlag POWER_MENU_LITE =
- new ReleasedFlag(300);
-
- /***************************************/
- // 400 - smartspace
- public static final ReleasedFlag SMARTSPACE_DEDUPING =
- new ReleasedFlag(400);
-
- public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
- new ReleasedFlag(401);
-
- public static final ResourceBooleanFlag SMARTSPACE =
- new ResourceBooleanFlag(402, R.bool.flag_smartspace);
-
- /***************************************/
- // 500 - quick settings
- /**
- * @deprecated Not needed anymore
- */
- @Deprecated
- public static final ReleasedFlag NEW_USER_SWITCHER =
- new ReleasedFlag(500);
-
- public static final UnreleasedFlag COMBINED_QS_HEADERS =
- new UnreleasedFlag(501, true);
-
- public static final ResourceBooleanFlag PEOPLE_TILE =
- new ResourceBooleanFlag(502, R.bool.flag_conversations);
-
- public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
- new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
-
- /**
- * @deprecated Not needed anymore
- */
- @Deprecated
- public static final ReleasedFlag NEW_FOOTER = new ReleasedFlag(504);
-
- public static final UnreleasedFlag NEW_HEADER = new UnreleasedFlag(505, true);
- public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
- new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
-
- public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
-
- /***************************************/
- // 600- status bar
- public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
- new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
-
- public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
- new ReleasedFlag(603, false);
-
- public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
- new UnreleasedFlag(604, false);
-
- public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
- new UnreleasedFlag(605, false);
-
- /***************************************/
- // 700 - dialer/calls
- public static final ReleasedFlag ONGOING_CALL_STATUS_BAR_CHIP =
- new ReleasedFlag(700);
-
- public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE =
- new ReleasedFlag(701);
-
- public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
- new ReleasedFlag(702);
-
- /***************************************/
- // 800 - general visual/theme
- public static final ResourceBooleanFlag MONET =
- new ResourceBooleanFlag(800, R.bool.flag_monet);
-
- /***************************************/
- // 801 - region sampling
- public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
-
- // 802 - wallpaper rendering
- public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
-
- // 803 - screen contents translation
- public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
-
- /***************************************/
- // 900 - media
- public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
- public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
- public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
- public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
- public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
- public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
- public static final UnreleasedFlag UMO_SURFACE_RIPPLE = new UnreleasedFlag(907);
-
- // 1000 - dock
- public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
- new ReleasedFlag(1000);
- public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
-
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
- new UnreleasedFlag(1002, /* teamfood= */ true);
-
- public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true);
-
- // 1100 - windowing
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
- new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
-
- /**
- * b/170163464: animate bubbles expanded view collapse with home gesture
- */
- @Keep
- public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
- new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true);
-
- @Keep
- public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING =
- new DeviceConfigBooleanFlag(1102, "record_task_content",
- NAMESPACE_WINDOW_MANAGER, false, true);
-
- @Keep
- public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
- new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
-
- @Keep
- public static final SysPropBooleanFlag WM_DESKTOP_WINDOWING =
- new SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false);
-
- @Keep
- public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL =
- new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false);
-
- @Keep
- public static final SysPropBooleanFlag FLOATING_TASKS_ENABLED =
- new SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false);
-
- @Keep
- public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
- new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
-
- @Keep
- public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
- new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
- @Keep
- public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
- new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
-
- @Keep
- public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
-
- // 1200 - predictive back
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
- 1200, "persist.wm.debug.predictive_back", true);
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK_ANIM = new SysPropBooleanFlag(
- 1201, "persist.wm.debug.predictive_back_anim", false);
- @Keep
- public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
- new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
-
- public static final UnreleasedFlag NEW_BACK_AFFORDANCE =
- new UnreleasedFlag(1203, false /* teamfood */);
-
- // 1300 - screenshots
-
- public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
- public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
-
- // 1400 - columbus
- public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
-
- // 1500 - chooser
- public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
-
- // 1600 - accessibility
- public static final UnreleasedFlag A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
- new UnreleasedFlag(1600);
-
- // 1700 - clipboard
- public static final UnreleasedFlag CLIPBOARD_OVERLAY_REFACTOR = new UnreleasedFlag(1700);
-
- // Pay no attention to the reflection behind the curtain.
- // ========================== Curtain ==========================
- // | |
- // | . . . . . . . . . . . . . . . . . . . |
- private static Map<Integer, Flag<?>> sFlagMap;
- static Map<Integer, Flag<?>> collectFlags() {
- if (sFlagMap != null) {
- return sFlagMap;
- }
-
- Map<Integer, Flag<?>> flags = new HashMap<>();
- List<Field> flagFields = getFlagFields();
-
- for (Field field : flagFields) {
- try {
- Flag<?> flag = (Flag<?>) field.get(null);
- flags.put(flag.getId(), flag);
- } catch (IllegalAccessException e) {
- // no-op
- }
- }
-
- sFlagMap = flags;
-
- return sFlagMap;
- }
-
- static List<Field> getFlagFields() {
- Field[] fields = Flags.class.getFields();
- List<Field> result = new ArrayList<>();
-
- for (Field field : fields) {
- Class<?> t = field.getType();
- if (Flag.class.isAssignableFrom(t)) {
- result.add(field);
- }
- }
-
- return result;
- }
- // | . . . . . . . . . . . . . . . . . . . |
- // | |
- // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
new file mode 100644
index 000000000000..9bd3cb17d66e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.provider.DeviceConfig
+import com.android.internal.annotations.Keep
+import com.android.systemui.R
+import java.lang.reflect.Field
+
+/**
+ * List of [Flag] objects for use in SystemUI.
+ *
+ * Flag Ids are integers. Ids must be unique. This is enforced in a unit test. Ids need not be
+ * sequential. Flags can "claim" a chunk of ids for flags in related features with a comment. This
+ * is purely for organizational purposes.
+ *
+ * On public release builds, flags will always return their default value. There is no way to change
+ * their value on release builds.
+ *
+ * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
+ */
+object Flags {
+ @JvmField val TEAMFOOD = UnreleasedFlag(1)
+
+ // 100 - notification
+ // TODO(b/254512751): Tracking Bug
+ val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = UnreleasedFlag(103)
+
+ // TODO(b/254512732): Tracking Bug
+ @JvmField val NSSL_DEBUG_LINES = UnreleasedFlag(105)
+
+ // TODO(b/254512505): Tracking Bug
+ @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = UnreleasedFlag(106)
+
+ // TODO(b/254512624): Tracking Bug
+ @JvmField
+ val NOTIFICATION_DRAG_TO_CONTENTS =
+ ResourceBooleanFlag(108, R.bool.config_notificationToContents)
+
+ // TODO(b/254512517): Tracking Bug
+ val FSI_REQUIRES_KEYGUARD = UnreleasedFlag(110, teamfood = true)
+
+ // TODO(b/254512538): Tracking Bug
+ val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
+
+ // TODO(b/254512425): Tracking Bug
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+
+ // TODO(b/254512731): Tracking Bug
+ @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
+ val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
+ val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+ // next id: 116
+
+ // 200 - keyguard/lockscreen
+ // ** Flag retired **
+ // public static final BooleanFlag KEYGUARD_LAYOUT =
+ // new BooleanFlag(200, true);
+ // TODO(b/254512713): Tracking Bug
+ @JvmField val LOCKSCREEN_ANIMATIONS = ReleasedFlag(201)
+
+ // TODO(b/254512750): Tracking Bug
+ val NEW_UNLOCK_SWIPE_ANIMATION = ReleasedFlag(202)
+ val CHARGING_RIPPLE = ResourceBooleanFlag(203, R.bool.flag_charging_ripple)
+
+ // TODO(b/254512281): Tracking Bug
+ @JvmField
+ val BOUNCER_USER_SWITCHER = ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher)
+
+ // TODO(b/254512694): Tracking Bug
+ val FACE_SCANNING_ANIM = ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation)
+
+ // TODO(b/254512676): Tracking Bug
+ @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = UnreleasedFlag(207, teamfood = true)
+
+ /**
+ * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
+ * replacement of KeyguardBouncer.java.
+ */
+ // TODO(b/254512385): Tracking Bug
+ @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+
+ /**
+ * Whether the user interactor and repository should use `UserSwitcherController`.
+ *
+ * If this is `false`, the interactor and repo skip the controller and directly access the
+ * framework APIs.
+ */
+ // TODO(b/254513286): Tracking Bug
+ val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = UnreleasedFlag(210)
+
+ /**
+ * Whether `UserSwitcherController` should use the user interactor.
+ *
+ * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
+ * through the interactor.
+ *
+ * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
+ * would created a cycle between controller -> interactor -> controller.
+ */
+ // TODO(b/254513102): Tracking Bug
+ val USER_CONTROLLER_USES_INTERACTOR = ReleasedFlag(211)
+
+ /**
+ * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
+ * the digits when the clock moves.
+ */
+ @JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+
+ // 300 - power menu
+ // TODO(b/254512600): Tracking Bug
+ @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
+
+ // 400 - smartspace
+
+ // TODO(b/254513100): Tracking Bug
+ val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = ReleasedFlag(401)
+ val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
+
+ // 500 - quick settings
+ @Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
+
+ // TODO(b/254512321): Tracking Bug
+ @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
+ val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+ @JvmField
+ val QS_USER_DETAIL_SHORTCUT =
+ ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
+
+ // TODO(b/254512699): Tracking Bug
+ @Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
+
+ // TODO(b/254512747): Tracking Bug
+ val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+
+ // TODO(b/254512383): Tracking Bug
+ @JvmField
+ val FULL_SCREEN_USER_SWITCHER =
+ ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher)
+
+ // TODO(b/254512678): Tracking Bug
+ @JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+
+ // 600- status bar
+ // TODO(b/254513246): Tracking Bug
+ val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
+
+ // TODO(b/254513025): Tracking Bug
+ val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
+
+ // TODO(b/254512623): Tracking Bug
+ @Deprecated("Replaced by mobile and wifi specific flags.")
+ val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
+
+ // TODO(b/254512660): Tracking Bug
+ @Deprecated("Replaced by mobile and wifi specific flags.")
+ val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+
+ val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606, false)
+
+ val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607, false)
+
+ // 700 - dialer/calls
+ // TODO(b/254512734): Tracking Bug
+ val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
+
+ // TODO(b/254512681): Tracking Bug
+ val ONGOING_CALL_IN_IMMERSIVE = ReleasedFlag(701)
+
+ // TODO(b/254512753): Tracking Bug
+ val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = ReleasedFlag(702)
+
+ // 800 - general visual/theme
+ @JvmField val MONET = ResourceBooleanFlag(800, R.bool.flag_monet)
+
+ // 801 - region sampling
+ // TODO(b/254512848): Tracking Bug
+ val REGION_SAMPLING = UnreleasedFlag(801)
+
+ // 802 - wallpaper rendering
+ // TODO(b/254512923): Tracking Bug
+ @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
+
+ // 803 - screen contents translation
+ // TODO(b/254513187): Tracking Bug
+ val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
+
+ // 804 - monochromatic themes
+ @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+
+ // 900 - media
+ // TODO(b/254512697): Tracking Bug
+ val MEDIA_TAP_TO_TRANSFER = ReleasedFlag(900)
+
+ // TODO(b/254512502): Tracking Bug
+ val MEDIA_SESSION_ACTIONS = UnreleasedFlag(901)
+
+ // TODO(b/254512726): Tracking Bug
+ val MEDIA_NEARBY_DEVICES = ReleasedFlag(903)
+
+ // TODO(b/254512695): Tracking Bug
+ val MEDIA_MUTE_AWAIT = ReleasedFlag(904)
+
+ // TODO(b/254512654): Tracking Bug
+ @JvmField val DREAM_MEDIA_COMPLICATION = UnreleasedFlag(905)
+
+ // TODO(b/254512673): Tracking Bug
+ @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
+
+ // TODO(b/254513168): Tracking Bug
+ val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+
+ // 1000 - dock
+ val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
+
+ // TODO(b/254512444): Tracking Bug
+ @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
+
+ // TODO(b/254512758): Tracking Bug
+ @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
+
+ // TODO(b/254512525): Tracking Bug
+ @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
+
+ // 1100 - windowing
+ @Keep
+ val WM_ENABLE_SHELL_TRANSITIONS =
+ SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
+
+ /** b/170163464: animate bubbles expanded view collapse with home gesture */
+ @Keep
+ val BUBBLES_HOME_GESTURE =
+ SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
+
+ // TODO(b/254513207): Tracking Bug
+ @JvmField
+ @Keep
+ val WM_ENABLE_PARTIAL_SCREEN_SHARING =
+ DeviceConfigBooleanFlag(
+ 1102,
+ "record_task_content",
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ false,
+ teamfood = true
+ )
+
+ // TODO(b/254512674): Tracking Bug
+ @JvmField
+ @Keep
+ val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+
+ @Keep
+ val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+
+ @Keep
+ val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+
+ @Keep
+ val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+
+ @Keep
+ val SHOW_FLOATING_TASKS_AS_BUBBLES =
+ SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+
+ @Keep
+ val ENABLE_FLING_TO_DISMISS_BUBBLE =
+ SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+
+ @Keep
+ val ENABLE_FLING_TO_DISMISS_PIP =
+ SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+
+ @Keep
+ val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+ SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+
+ // 1200 - predictive back
+ @Keep
+ val WM_ENABLE_PREDICTIVE_BACK =
+ SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+
+ @Keep
+ val WM_ENABLE_PREDICTIVE_BACK_ANIM =
+ SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+
+ @Keep
+ val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
+ SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
+
+ // TODO(b/254512728): Tracking Bug
+ @JvmField val NEW_BACK_AFFORDANCE = UnreleasedFlag(1203, teamfood = false)
+
+ // 1300 - screenshots
+ // TODO(b/254512719): Tracking Bug
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+
+ // TODO(b/254513155): Tracking Bug
+ @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
+
+ // 1400 - columbus
+ // TODO(b/254512756): Tracking Bug
+ val QUICK_TAP_IN_PCC = ReleasedFlag(1400)
+
+ // 1500 - chooser
+ // TODO(b/254512507): Tracking Bug
+ val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+
+ // 1600 - accessibility
+ @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
+
+ // 1700 - clipboard
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+
+ // 1800 - shade container
+ @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ @JvmStatic
+ fun collectFlags(): Map<Int, Flag<*>> {
+ return flagFields
+ .map { field ->
+ // field[null] returns the current value of the field.
+ // See java.lang.Field#get
+ val flag = field[null] as Flag<*>
+ flag.id to flag
+ }
+ .toMap()
+ }
+
+ // | . . . . . . . . . . . . . . . . . . . |
+ @JvmStatic
+ val flagFields: List<Field>
+ get() {
+ return Flags::class.java.fields.filter { f ->
+ Flag::class.java.isAssignableFrom(f.type)
+ }
+ }
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index da5819a7f3bc..3ef5499237f1 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -116,6 +116,7 @@ import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -448,10 +449,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
*
* @param keyguardShowing True if keyguard is showing
* @param isDeviceProvisioned True if device is provisioned
- * @param view The view from which we should animate the dialog when showing it
+ * @param expandable The expandable from which we should animate the dialog when
+ * showing it
*/
public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
- @Nullable View view) {
+ @Nullable Expandable expandable) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null && mDialog.isShowing()) {
@@ -463,7 +465,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialog.dismiss();
mDialog = null;
} else {
- handleShow(view);
+ handleShow(expandable);
}
}
@@ -495,7 +497,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
}
- protected void handleShow(@Nullable View view) {
+ protected void handleShow(@Nullable Expandable expandable) {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
@@ -507,10 +509,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
// Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
- if (view != null) {
- mDialogLaunchAnimator.showFromView(mDialog, view,
- new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
+ DialogLaunchAnimator.Controller controller =
+ expandable != null ? expandable.dialogLaunchController(
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG)) : null;
+ if (controller != null) {
+ mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}
@@ -1016,8 +1020,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
Log.w(TAG, "Bugreport handler could not be launched");
mIActivityManager.requestInteractiveBugReport();
}
- // Close shade so user sees the activity
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+ // Maybe close shade (depends on a flag) so user sees the activity
+ mCentralSurfacesOptional.ifPresent(
+ CentralSurfaces::collapseShadeForBugreport);
} catch (RemoteException e) {
}
}
@@ -1036,8 +1041,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
mIActivityManager.requestFullBugReport();
- // Close shade so user sees the activity
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+ // Maybe close shade (depends on a flag) so user sees the activity
+ mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShadeForBugreport);
} catch (RemoteException e) {
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 84bd8cec51c8..0d74dc850dda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -401,6 +401,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final float mWindowCornerRadius;
/**
+ * The duration in milliseconds of the dream open animation.
+ */
+ private final int mDreamOpenAnimationDuration;
+
+ /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -751,6 +756,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "keyguardGone");
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
mKeyguardDisplayManager.hide();
+ mUpdateMonitor.startBiometricWatchdog();
Trace.endSection();
}
@@ -946,8 +952,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
- // Use the same duration as for the UNOCCLUDE.
- mOccludeByDreamAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+ mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
@@ -1179,6 +1184,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mPowerButtonY = context.getResources().getDimensionPixelSize(
R.dimen.physical_power_button_center_screen_location_y);
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+
+ mDreamOpenAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_dreamOpenAnimationDuration);
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56f1ac46a875..56a1f1ae936e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -72,6 +73,7 @@ import dagger.Provides;
FalsingModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
+ StartKeyguardTransitionModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 45b668e609ea..b186ae0ceec4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -85,6 +86,9 @@ interface KeyguardRepository {
*/
val dozeAmount: Flow<Float>
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -185,6 +189,24 @@ constructor(
return keyguardStateController.isShowing
}
+ override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(
+ statusBarStateIntToObject(statusBarStateController.getState()),
+ TAG,
+ "initial state"
+ )
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -197,6 +219,15 @@ constructor(
_clockPosition.value = Position(x, y)
}
+ private fun statusBarStateIntToObject(value: Int): StatusBarState {
+ return when (value) {
+ 0 -> StatusBarState.SHADE
+ 1 -> StatusBarState.KEYGUARD
+ 2 -> StatusBarState.SHADE_LOCKED
+ else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
new file mode 100644
index 000000000000..e8532ecfdc37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.annotation.FloatRange
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class KeyguardTransitionRepository @Inject constructor() {
+ /*
+ * Each transition between [KeyguardState]s will have an associated Flow.
+ * In order to collect these events, clients should call [transition].
+ */
+ private val _transitions = MutableStateFlow(TransitionStep())
+ val transitions = _transitions.asStateFlow()
+
+ /* Information about the active transition. */
+ private var currentTransitionInfo: TransitionInfo? = null
+ /*
+ * When manual control of the transition is requested, a unique [UUID] is used as the handle
+ * to permit calls to [updateTransition]
+ */
+ private var updateTransitionId: UUID? = null
+
+ /**
+ * Interactors that require information about changes between [KeyguardState]s will call this to
+ * register themselves for flowable [TransitionStep]s when that transition occurs.
+ */
+ fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+ return transitions.filter { step -> step.from == from && step.to == to }
+ }
+
+ /**
+ * Begin a transition from one state to another. The [info.from] must match
+ * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
+ * unplanned transitions.
+ */
+ fun startTransition(info: TransitionInfo): UUID? {
+ if (currentTransitionInfo != null) {
+ // Open questions:
+ // * Queue of transitions? buffer of 1?
+ // * Are transitions cancellable if a new one is triggered?
+ // * What validation does this need to do?
+ Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
+ return null
+ }
+ currentTransitionInfo?.animator?.cancel()
+
+ currentTransitionInfo = info
+ info.animator?.let { animator ->
+ // An animator was provided, so use it to run the transition
+ animator.setFloatValues(0f, 1f)
+ val updateListener =
+ object : AnimatorUpdateListener {
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ emitTransition(
+ info,
+ (animation.getAnimatedValue() as Float),
+ TransitionState.RUNNING
+ )
+ }
+ }
+ val adapter =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ Log.i(TAG, "Starting transition: $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+ }
+ override fun onAnimationCancel(animation: Animator) {
+ Log.i(TAG, "Cancelling transition: $info")
+ }
+ override fun onAnimationEnd(animation: Animator) {
+ Log.i(TAG, "Ending transition: $info")
+ emitTransition(info, 1f, TransitionState.FINISHED)
+ animator.removeListener(this)
+ animator.removeUpdateListener(updateListener)
+ }
+ }
+ animator.addListener(adapter)
+ animator.addUpdateListener(updateListener)
+ animator.start()
+ return@startTransition null
+ }
+ ?: run {
+ Log.i(TAG, "Starting transition (manual): $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+
+ // No animator, so it's manual. Provide a mechanism to callback
+ updateTransitionId = UUID.randomUUID()
+ return@startTransition updateTransitionId
+ }
+ }
+
+ /**
+ * Allows manual control of a transition. When calling [startTransition], the consumer must pass
+ * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
+ * further updates.
+ *
+ * When the transition is over, TransitionState.FINISHED must be passed into the [state]
+ * parameter.
+ */
+ fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) {
+ if (updateTransitionId != transitionId) {
+ Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ return
+ }
+
+ if (currentTransitionInfo == null) {
+ Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
+ return
+ }
+
+ currentTransitionInfo?.let { info ->
+ if (state == TransitionState.FINISHED) {
+ updateTransitionId = null
+ Log.i(TAG, "Ending transition: $info")
+ }
+
+ emitTransition(info, value, state)
+ }
+ }
+
+ private fun emitTransition(
+ info: TransitionInfo,
+ value: Float,
+ transitionState: TransitionState
+ ) {
+ if (transitionState == TransitionState.FINISHED) {
+ currentTransitionInfo = null
+ }
+ _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
new file mode 100644
index 000000000000..400376663f1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("AOD<->LOCKSCREEN") {
+
+ override fun start() {
+ scope.launch {
+ keyguardRepository.isDozing.collect { isDozing ->
+ if (isDozing) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ } else {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 7d4db37c6b0f..2af9318d92ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -273,8 +273,8 @@ constructor(
/** Tell the bouncer to start the pre hide animation. */
fun startDisappearAnimation(runnable: Runnable) {
val finishRunnable = Runnable {
- repository.setStartDisappearAnimation(null)
runnable.run()
+ repository.setStartDisappearAnimation(null)
}
repository.setStartDisappearAnimation(finishRunnable)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 192919e32cf6..fc2269c6b01c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -38,7 +38,7 @@ constructor(
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing ot not. */
+ /** Whether the keyguard is showing to not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
fun isKeyguardShowing(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
new file mode 100644
index 000000000000..b166681433a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import java.util.Set
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardTransitionCoreStartable
+@Inject
+constructor(
+ private val interactors: Set<TransitionInteractor>,
+) : CoreStartable {
+
+ override fun start() {
+ // By listing the interactors in a when, the compiler will help enforce all classes
+ // extending the sealed class [TransitionInteractor] will be initialized.
+ interactors.forEach {
+ // `when` needs to be an expression in order for the compiler to enforce it being
+ // exhaustive
+ val ret =
+ when (it) {
+ is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ }
+ it.start()
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionCoreStartable"
+ }
+}
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
new file mode 100644
index 000000000000..59bb22786917
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard transitions. */
+@SysUISingleton
+class KeyguardTransitionInteractor
+@Inject
+constructor(
+ repository: KeyguardTransitionRepository,
+) {
+ /** AOD->LOCKSCREEN transition information. */
+ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
new file mode 100644
index 000000000000..3c2a12e3836a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class LockscreenBouncerTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ scope.launch {
+ shadeRepository.shadeModel.sample(
+ combine(
+ keyguardTransitionRepository.transitions,
+ keyguardRepository.statusBarState,
+ ) { transitions, statusBarState ->
+ Pair(transitions, statusBarState)
+ }
+ ) { shadeModel, pair ->
+ val (transitions, statusBarState) = pair
+
+ val id = transitionId
+ if (id != null) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED
+ keyguardTransitionRepository.updateTransition(
+ id,
+ shadeModel.expansionAmount,
+ if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
+ transitionId = null
+ TransitionState.FINISHED
+ } else {
+ TransitionState.RUNNING
+ }
+ )
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is integrated
+ // into KeyguardTransitionRepository
+ val isOnLockscreen =
+ transitions.transitionState == TransitionState.FINISHED &&
+ transitions.to == KeyguardState.LOCKSCREEN
+ if (
+ isOnLockscreen &&
+ shadeModel.isUserDragging &&
+ statusBarState != SHADE_LOCKED
+ ) {
+ transitionId =
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = null,
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
new file mode 100644
index 000000000000..74c542c0043f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class StartKeyguardTransitionModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardTransitionCoreStartable::class)
+ abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenBouncer(
+ impl: LockscreenBouncerTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+}
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
new file mode 100644
index 000000000000..a2a46d9e3a71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+/**
+ * Each TransitionInteractor is responsible for determining under which conditions to notify
+ * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
+ * determined by [KeyguardTransitionRepository].
+ *
+ * [name] field should be a unique identifiable string representing this state, used primarily for
+ * logging
+ *
+ * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
+ * 'when' clause of [KeyguardTransitionCoreStartable]
+ */
+sealed class TransitionInteractor(val name: String) {
+
+ abstract fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
new file mode 100644
index 000000000000..f66d5d3650c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** List of all possible states to transition to/from */
+enum class KeyguardState {
+ /** For initialization only */
+ NONE,
+ /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+ AOD,
+ /*
+ * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
+ * (Fingerprint Sensor) variations, for the user to verify their credentials
+ */
+ BOUNCER,
+ /*
+ * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
+ * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
+ */
+ LOCKSCREEN,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
new file mode 100644
index 000000000000..bb953477583d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** See [com.android.systemui.statusbar.StatusBarState] for definitions */
+enum class StatusBarState {
+ SHADE,
+ KEYGUARD,
+ SHADE_LOCKED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
new file mode 100644
index 000000000000..bfccf3fe076c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import android.animation.ValueAnimator
+
+/** Tracks who is controlling the current transition, and how to run it. */
+data class TransitionInfo(
+ val ownerName: String,
+ val from: KeyguardState,
+ val to: KeyguardState,
+ val animator: ValueAnimator?, // 'null' animator signal manual control
+) {
+ override fun toString(): String =
+ "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
+ (if (animator != null) {
+ "animated"
+ } else {
+ "manual"
+ }) +
+ ")"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
new file mode 100644
index 000000000000..d8691c17f53d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Possible states for a running transition between [State] */
+enum class TransitionState {
+ NONE,
+ STARTED,
+ RUNNING,
+ FINISHED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
new file mode 100644
index 000000000000..688ec912aac8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
+data class TransitionStep(
+ val from: KeyguardState = KeyguardState.NONE,
+ val to: KeyguardState = KeyguardState.NONE,
+ val value: Float = 0f, // constrained [0.0, 1.0]
+ val transitionState: TransitionState = TransitionState.NONE,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 00bf2104b7f2..5897087019f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -43,7 +43,7 @@ public class LogModule {
@SysUISingleton
@DozeLog
public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
- return factory.create("DozeLog", 120);
+ return factory.create("DozeLog", 150);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 1ac2a078c8a0..be357ee0ff73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -182,8 +182,7 @@ class MediaProjectionAppSelectorActivity(
override fun shouldGetOnlyDefaultActivities() = false
- // TODO(b/240924732) flip the flag when the recents selector is ready
- override fun shouldShowContentPreview() = false
+ override fun shouldShowContentPreview() = true
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
recentsViewController.createView(parent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index c3de94f28aea..0a6043793ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -21,6 +21,8 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -31,6 +33,23 @@ class MediaTttUtils {
const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
/**
+ * Returns the information needed to display the icon in [Icon] form.
+ *
+ * See [getIconInfoFromPackageName].
+ */
+ fun getIconFromPackageName(
+ context: Context,
+ appPackageName: String?,
+ logger: MediaTttLogger,
+ ): Icon {
+ val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
+ return Icon.Loaded(
+ iconInfo.drawable,
+ ContentDescription.Loaded(iconInfo.contentDescription)
+ )
+ }
+
+ /**
* Returns the information needed to display the icon.
*
* The information will either contain app name and icon of the app playing media, or a
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index c24b0307fcd1..6e596ee1f473 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -18,17 +18,12 @@ package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
-import android.media.MediaRoute2Info
import android.util.Log
-import android.view.View
import androidx.annotation.StringRes
import com.android.internal.logging.UiEventLogger
-import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
-import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
-import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
-import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
/**
* A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -38,6 +33,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
* @property stringResId the res ID of the string that should be displayed in the chip. Null if the
* state should not have the chip be displayed.
* @property transferStatus the transfer status that the chip state represents.
+ * @property endItem the item that should be displayed in the end section of the chip.
* @property timeout the amount of time this chip should display on the screen before it times out
* and disappears.
*/
@@ -46,6 +42,7 @@ enum class ChipStateSender(
val uiEvent: UiEventLogger.UiEventEnum,
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
+ val endItem: SenderEndItem?,
val timeout: Long = DEFAULT_TIMEOUT_MILLIS
) {
/**
@@ -58,6 +55,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST,
R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED,
+ endItem = null,
),
/**
@@ -71,6 +69,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST,
R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED,
+ endItem = null,
),
/**
@@ -82,6 +81,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED,
R.string.media_transfer_playing_different_device,
transferStatus = TransferStatus.IN_PROGRESS,
+ endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -94,6 +94,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
R.string.media_transfer_playing_this_device,
transferStatus = TransferStatus.IN_PROGRESS,
+ endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -105,36 +106,13 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED,
R.string.media_transfer_playing_different_device,
transferStatus = TransferStatus.SUCCEEDED,
- ) {
- override fun undoClickListener(
- chipbarCoordinator: ChipbarCoordinator,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? {
- if (undoCallback == null) {
- return null
- }
- return View.OnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
- uiEventLogger.logUndoClicked(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
- )
- undoCallback.onUndoTriggered()
- // The external service should eventually send us a TransferToThisDeviceTriggered
- // state, but that may take too long to go through the binder and the user may be
- // confused as to why the UI hasn't changed yet. So, we immediately change the UI
- // here.
- chipbarCoordinator.displayView(
- ChipSenderInfo(
- TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback
- )
- )
- }
- }
- },
+ endItem = SenderEndItem.UndoButton(
+ uiEventOnClick =
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED,
+ newState =
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ ),
+ ),
/**
* A state representing that a transfer back to this device has been successfully completed.
@@ -144,36 +122,13 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
R.string.media_transfer_playing_this_device,
transferStatus = TransferStatus.SUCCEEDED,
- ) {
- override fun undoClickListener(
- chipbarCoordinator: ChipbarCoordinator,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? {
- if (undoCallback == null) {
- return null
- }
- return View.OnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
- uiEventLogger.logUndoClicked(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
- )
- undoCallback.onUndoTriggered()
- // The external service should eventually send us a TransferToReceiverTriggered
- // state, but that may take too long to go through the binder and the user may be
- // confused as to why the UI hasn't changed yet. So, we immediately change the UI
- // here.
- chipbarCoordinator.displayView(
- ChipSenderInfo(
- TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback
- )
- )
- }
- }
- },
+ endItem = SenderEndItem.UndoButton(
+ uiEventOnClick =
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED,
+ newState =
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
+ ),
+ ),
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
@@ -181,6 +136,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED,
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
+ endItem = SenderEndItem.Error,
),
/** A state representing that a transfer back to this device has failed. */
@@ -189,6 +145,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED,
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
+ endItem = SenderEndItem.Error,
),
/** A state representing that this device is far away from any receiver device. */
@@ -197,37 +154,27 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER,
stringResId = null,
transferStatus = TransferStatus.TOO_FAR,
- );
+ // We shouldn't be displaying the chipbar anyway
+ endItem = null,
+ ) {
+ override fun getChipTextString(context: Context, otherDeviceName: String): Text {
+ // TODO(b/245610654): Better way to handle this.
+ throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
+ "so its string should never be fetched")
+ }
+ };
/**
* Returns a fully-formed string with the text that the chip should display.
*
+ * Throws an NPE if [stringResId] is null.
+ *
* @param otherDeviceName the name of the other device involved in the transfer.
*/
- fun getChipTextString(context: Context, otherDeviceName: String): String? {
- if (stringResId == null) {
- return null
- }
- return context.getString(stringResId, otherDeviceName)
+ open fun getChipTextString(context: Context, otherDeviceName: String): Text {
+ return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
- /**
- * Returns a click listener for the undo button on the chip. Returns null if this chip state
- * doesn't have an undo button.
- *
- * @param chipbarCoordinator passed as a parameter in case we want to display a new chipbar
- * when undo is clicked.
- * @param undoCallback if present, the callback that should be called when the user clicks the
- * undo button. The undo button will only be shown if this is non-null.
- */
- open fun undoClickListener(
- chipbarCoordinator: ChipbarCoordinator,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? = null
-
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
@@ -253,6 +200,26 @@ enum class ChipStateSender(
}
}
+/** Represents the item that should be displayed in the end section of the chip. */
+sealed class SenderEndItem {
+ /** A loading icon should be displayed. */
+ object Loading : SenderEndItem()
+
+ /** An error icon should be displayed. */
+ object Error : SenderEndItem()
+
+ /**
+ * An undo button should be displayed.
+ *
+ * @property uiEventOnClick the UI event to log when this button is clicked.
+ * @property newState the state that should immediately be transitioned to.
+ */
+ data class UndoButton(
+ val uiEventOnClick: UiEventLogger.UiEventEnum,
+ @StatusBarManager.MediaTransferSenderState val newState: Int,
+ ) : SenderEndItem()
+}
+
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 224303ac098c..edf759ddfd22 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -20,14 +20,20 @@ import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
import android.util.Log
+import android.view.View
+import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
import javax.inject.Inject
@@ -107,7 +113,90 @@ constructor(
chipbarCoordinator.removeView(removalReason)
} else {
displayedState = chipState
- chipbarCoordinator.displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
+ chipbarCoordinator.displayView(
+ createChipbarInfo(
+ chipState,
+ routeInfo,
+ undoCallback,
+ context,
+ logger,
+ )
+ )
}
}
+
+ /**
+ * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+ */
+ private fun createChipbarInfo(
+ chipStateSender: ChipStateSender,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?,
+ context: Context,
+ logger: MediaTttLogger,
+ ): ChipbarInfo {
+ val packageName = routeInfo.clientPackageName
+ val otherDeviceName = routeInfo.name.toString()
+
+ return ChipbarInfo(
+ // Display the app's icon as the start icon
+ startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
+ text = chipStateSender.getChipTextString(context, otherDeviceName),
+ endItem =
+ when (chipStateSender.endItem) {
+ null -> null
+ is SenderEndItem.Loading -> ChipbarEndItem.Loading
+ is SenderEndItem.Error -> ChipbarEndItem.Error
+ is SenderEndItem.UndoButton -> {
+ if (undoCallback != null) {
+ getUndoButton(
+ undoCallback,
+ chipStateSender.endItem.uiEventOnClick,
+ chipStateSender.endItem.newState,
+ routeInfo,
+ )
+ } else {
+ null
+ }
+ }
+ },
+ vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ )
+ }
+
+ /**
+ * Returns an undo button for the chip.
+ *
+ * When the button is clicked: [undoCallback] will be triggered, [uiEvent] will be logged, and
+ * this coordinator will transition to [newState].
+ */
+ private fun getUndoButton(
+ undoCallback: IUndoMediaTransferCallback,
+ uiEvent: UiEventLogger.UiEventEnum,
+ @StatusBarManager.MediaTransferSenderState newState: Int,
+ routeInfo: MediaRoute2Info,
+ ): ChipbarEndItem.Button {
+ val onClickListener =
+ View.OnClickListener {
+ uiEventLogger.logUndoClicked(uiEvent)
+ undoCallback.onUndoTriggered()
+
+ // The external service should eventually send us a new TransferTriggered state, but
+ // but that may take too long to go through the binder and the user may be confused
+ // as to why the UI hasn't changed yet. So, we immediately change the UI here.
+ updateMediaTapToTransferSenderDisplay(
+ newState,
+ routeInfo,
+ // Since we're force-updating the UI, we don't have any [undoCallback] from the
+ // external service (and TransferTriggered states don't have undo callbacks
+ // anyway).
+ undoCallback = null,
+ )
+ }
+
+ return ChipbarEndItem.Button(
+ Text.Resource(R.string.media_transfer_undo),
+ onClickListener,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
index f15720df5245..b96380976dec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
@@ -16,16 +16,36 @@
package com.android.systemui.media.taptotransfer.sender
-/** Represents the different possible transfer states that we could be in. */
-enum class TransferStatus {
+import android.os.VibrationEffect
+
+/**
+ * Represents the different possible transfer states that we could be in and the vibration effects
+ * that come with updating transfer states.
+ *
+ * @property vibrationEffect an optional vibration effect when the transfer status is changed.
+ */
+enum class TransferStatus(
+ val vibrationEffect: VibrationEffect? = null,
+) {
/** The transfer hasn't started yet. */
- NOT_STARTED,
+ NOT_STARTED(
+ vibrationEffect =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 0)
+ .compose()
+ ),
/** The transfer is currently ongoing but hasn't completed yet. */
- IN_PROGRESS,
+ IN_PROGRESS(
+ vibrationEffect =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 0)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 70)
+ .compose(),
+ ),
/** The transfer has completed successfully. */
SUCCEEDED,
/** The transfer has completed with a failure. */
- FAILED,
+ FAILED(vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)),
/** The device is too far away to do a transfer. */
TOO_FAR,
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 482a1397642b..bb2b4419a80a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -52,6 +52,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -98,10 +99,10 @@ interface FgsManagerController {
fun init()
/**
- * Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
+ * Show the foreground services dialog. The dialog will be expanded from [expandable] if
* it's not `null`.
*/
- fun showDialog(viewLaunchedFrom: View?)
+ fun showDialog(expandable: Expandable?)
/** Add a [OnNumberOfPackagesChangedListener]. */
fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
@@ -367,7 +368,7 @@ class FgsManagerControllerImpl @Inject constructor(
override fun shouldUpdateFooterVisibility() = dialog == null
- override fun showDialog(viewLaunchedFrom: View?) {
+ override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
@@ -403,16 +404,18 @@ class FgsManagerControllerImpl @Inject constructor(
}
mainExecutor.execute {
- viewLaunchedFrom
- ?.let {
- dialogLaunchAnimator.showFromView(
- dialog, it,
- cuj = DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ val controller =
+ expandable?.dialogLaunchController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG,
)
- } ?: dialog.show()
+ )
+ if (controller != null) {
+ dialogLaunchAnimator.show(dialog, controller)
+ } else {
+ dialog.show()
+ }
}
backgroundExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 9d64781ef2e9..a9943e886339 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -32,6 +32,7 @@ import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -156,7 +157,7 @@ internal class FooterActionsController @Inject constructor(
startSettingsActivity()
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- globalActionsDialog?.showOrHideDialog(false, true, v)
+ globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 7511278e0919..b1b9dd721eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -29,6 +29,7 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.dagger.QSScope;
@@ -130,7 +131,7 @@ public class QSFgsManagerFooter implements View.OnClickListener,
@Override
public void onClick(View view) {
- mFgsManagerController.showDialog(mRootView);
+ mFgsManagerController.showDialog(Expandable.fromView(view));
}
public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 67bf3003deff..6c1e95645550 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.qualifiers.Background;
@@ -169,7 +170,7 @@ public class QSSecurityFooter extends ViewController<View>
// TODO(b/242040009): Remove this.
public void showDeviceMonitoringDialog() {
- mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, mView);
+ mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
}
public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index ae6ed2008a77..67bc76998597 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -75,6 +75,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.common.shared.model.ContentDescription;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.SysUISingleton;
@@ -190,8 +191,9 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
/** Show the device monitoring dialog. */
- public void showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable View view) {
- createDialog(quickSettingsContext, view);
+ public void showDeviceMonitoringDialog(Context quickSettingsContext,
+ @Nullable Expandable expandable) {
+ createDialog(quickSettingsContext, expandable);
}
/**
@@ -440,7 +442,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
}
- private void createDialog(Context quickSettingsContext, @Nullable View view) {
+ private void createDialog(Context quickSettingsContext, @Nullable Expandable expandable) {
mShouldUseSettingsButton.set(false);
mBgHandler.post(() -> {
String settingsButtonText = getSettingsButton();
@@ -453,9 +455,12 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
? settingsButtonText : getNegativeButton(), this);
mDialog.setView(dialogView);
- if (view != null && view.isAggregatedVisible()) {
- mDialogLaunchAnimator.showFromView(mDialog, view, new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
+ DialogLaunchAnimator.Controller controller =
+ expandable != null ? expandable.dialogLaunchController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG))
+ : null;
+ if (controller != null) {
+ mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index ac46c85c10a4..f37d66877069 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -34,10 +34,12 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
+import com.android.systemui.ProtoDumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -48,6 +50,7 @@ import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
@@ -59,16 +62,20 @@ import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.settings.SecureSettings;
+import org.jetbrains.annotations.NotNull;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -82,7 +89,7 @@ import javax.inject.Provider;
* This class also provides the interface for adding/removing/changing tiles.
*/
@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -671,4 +678,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
.forEach(o -> ((Dumpable) o).dump(pw, args));
}
+
+ @Override
+ public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
+ List<QsTileState> data = mTiles.values().stream()
+ .map(QSTile::getState)
+ .map(TileStateToProtoKt::toProto)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
new file mode 100644
index 000000000000..2c8a5a4981d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.qs
+
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.nano.QsTileState
+import com.android.systemui.util.nano.ComponentNameProto
+
+fun QSTile.State.toProto(): QsTileState? {
+ if (TextUtils.isEmpty(spec)) return null
+ val state = QsTileState()
+ if (spec.startsWith(CustomTile.PREFIX)) {
+ val protoComponentName = ComponentNameProto()
+ val tileComponentName = CustomTile.getComponentFromSpec(spec)
+ protoComponentName.packageName = tileComponentName.packageName
+ protoComponentName.className = tileComponentName.className
+ state.componentName = protoComponentName
+ } else {
+ state.spec = spec
+ }
+ state.state =
+ when (this.state) {
+ Tile.STATE_UNAVAILABLE -> QsTileState.UNAVAILABLE
+ Tile.STATE_INACTIVE -> QsTileState.INACTIVE
+ Tile.STATE_ACTIVE -> QsTileState.ACTIVE
+ else -> QsTileState.UNAVAILABLE
+ }
+ label?.let { state.label = it.toString() }
+ secondaryLabel?.let { state.secondaryLabel = it.toString() }
+ if (this is QSTile.BooleanState) {
+ state.booleanState = value
+ }
+ return state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index cf9b41c25388..9ba3501c3434 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -23,13 +23,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import android.provider.Settings
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.util.FrameworkStatsLog
-import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
@@ -74,37 +72,27 @@ interface FooterActionsInteractor {
val deviceMonitoringDialogRequests: Flow<Unit>
/**
- * Show the device monitoring dialog, expanded from [view].
- *
- * Important: [view] must be associated to the same [Context] as the [Quick Settings fragment]
- * [com.android.systemui.qs.QSFragment].
- */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showDeviceMonitoringDialog(view: View)
-
- /**
- * Show the device monitoring dialog.
+ * Show the device monitoring dialog, expanded from [expandable] if it's not null.
*
* Important: [quickSettingsContext] *must* be the [Context] associated to the [Quick Settings
* fragment][com.android.systemui.qs.QSFragment].
*/
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showDeviceMonitoringDialog(quickSettingsContext: Context)
+ fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
/** Show the foreground services dialog. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showForegroundServicesDialog(view: View)
+ fun showForegroundServicesDialog(expandable: Expandable)
/** Show the power menu dialog. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View)
+ fun showPowerMenuDialog(
+ globalActionsDialogLite: GlobalActionsDialogLite,
+ expandable: Expandable,
+ )
/** Show the settings. */
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showUserSwitcher(view: View)
+ fun showUserSwitcher(context: Context, expandable: Expandable)
}
@SysUISingleton
@@ -147,28 +135,32 @@ constructor(
null,
)
- override fun showDeviceMonitoringDialog(view: View) {
- qsSecurityFooterUtils.showDeviceMonitoringDialog(view.context, view)
- DevicePolicyEventLogger.createEvent(
- FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
- )
- .write()
- }
-
- override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
- qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, /* view= */ null)
+ override fun showDeviceMonitoringDialog(
+ quickSettingsContext: Context,
+ expandable: Expandable?,
+ ) {
+ qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+ if (expandable != null) {
+ DevicePolicyEventLogger.createEvent(
+ FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
+ )
+ .write()
+ }
}
- override fun showForegroundServicesDialog(view: View) {
- fgsManagerController.showDialog(view)
+ override fun showForegroundServicesDialog(expandable: Expandable) {
+ fgsManagerController.showDialog(expandable)
}
- override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
+ override fun showPowerMenuDialog(
+ globalActionsDialogLite: GlobalActionsDialogLite,
+ expandable: Expandable,
+ ) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialogLite.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
- view,
+ expandable,
)
}
@@ -189,21 +181,21 @@ constructor(
)
}
- override fun showUserSwitcher(view: View) {
+ override fun showUserSwitcher(context: Context, expandable: Expandable) {
if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(view)
+ userSwitchDialogController.showDialog(context, expandable)
return
}
val intent =
- Intent(view.context, UserSwitcherActivity::class.java).apply {
+ Intent(context, UserSwitcherActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
activityStarter.startActivity(
intent,
true /* dismissShade */,
- ActivityLaunchAnimator.Controller.fromView(view, null),
+ expandable.activityLaunchController(),
true /* showOverlockscreenwhenlocked */,
UserHandle.SYSTEM,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index dd1ffcc9fa12..3e39c8ee62f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -31,6 +31,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.people.ui.view.PeopleViewBinder.bind
@@ -125,7 +126,7 @@ object FooterActionsViewBinder {
launch {
viewModel.security.collect { security ->
if (previousSecurity != security) {
- bindSecurity(securityHolder, security)
+ bindSecurity(view.context, securityHolder, security)
previousSecurity = security
}
}
@@ -159,6 +160,7 @@ object FooterActionsViewBinder {
}
private fun bindSecurity(
+ quickSettingsContext: Context,
securityHolder: TextButtonViewHolder,
security: FooterActionsSecurityButtonViewModel?,
) {
@@ -171,9 +173,12 @@ object FooterActionsViewBinder {
// Make sure that the chevron is visible and that the button is clickable if there is a
// listener.
val chevron = securityHolder.chevron
- if (security.onClick != null) {
+ val onClick = security.onClick
+ if (onClick != null) {
securityView.isClickable = true
- securityView.setOnClickListener(security.onClick)
+ securityView.setOnClickListener {
+ onClick(quickSettingsContext, Expandable.fromView(securityView))
+ }
chevron.isVisible = true
} else {
securityView.isClickable = false
@@ -205,7 +210,9 @@ object FooterActionsViewBinder {
foregroundServicesWithNumberView.isVisible = false
foregroundServicesWithTextView.isVisible = true
- foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithTextView.setOnClickListener {
+ foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+ }
foregroundServicesWithTextHolder.text.text = foregroundServices.text
foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
} else {
@@ -213,7 +220,9 @@ object FooterActionsViewBinder {
foregroundServicesWithTextView.isVisible = false
foregroundServicesWithNumberView.visibility = View.VISIBLE
- foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithNumberView.setOnClickListener {
+ foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+ }
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
@@ -229,7 +238,7 @@ object FooterActionsViewBinder {
}
buttonView.setBackgroundResource(model.background)
- buttonView.setOnClickListener(model.onClick)
+ buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon
val iconView = button.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 9b5f683d8dab..8d819dacba67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.annotation.DrawableRes
-import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/**
@@ -29,7 +29,5 @@ data class FooterActionsButtonViewModel(
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
- // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
- // or activity.
- val onClick: (View) -> Unit,
+ val onClick: (Expandable) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
index 98b53cb0ed5a..ff8130d3e6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
-import android.view.View
+import com.android.systemui.animation.Expandable
/** A ViewModel for the foreground services button. */
data class FooterActionsForegroundServicesButtonViewModel(
@@ -24,5 +24,5 @@ data class FooterActionsForegroundServicesButtonViewModel(
val text: String,
val displayText: Boolean,
val hasNewChanges: Boolean,
- val onClick: (View) -> Unit,
+ val onClick: (Expandable) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
index 98ab129fc9de..3450505f9f86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
@@ -16,12 +16,13 @@
package com.android.systemui.qs.footer.ui.viewmodel
-import android.view.View
+import android.content.Context
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/** A ViewModel for the security button. */
data class FooterActionsSecurityButtonViewModel(
val icon: Icon,
val text: String,
- val onClick: ((View) -> Unit)?,
+ val onClick: ((quickSettingsContext: Context, Expandable) -> Unit)?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index d3c06f60bc90..dee6fadbc9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -18,7 +18,6 @@ package com.android.systemui.qs.footer.ui.viewmodel
import android.content.Context
import android.util.Log
-import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -199,50 +198,51 @@ class FooterActionsViewModel(
*/
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
footerActionsInteractor.deviceMonitoringDialogRequests.collect {
- footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext)
+ footerActionsInteractor.showDeviceMonitoringDialog(
+ quickSettingsContext,
+ expandable = null,
+ )
}
}
- private fun onSecurityButtonClicked(view: View) {
+ private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showDeviceMonitoringDialog(view)
+ footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
- private fun onForegroundServiceButtonClicked(view: View) {
+ private fun onForegroundServiceButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showForegroundServicesDialog(view)
+ footerActionsInteractor.showForegroundServicesDialog(expandable)
}
- private fun onUserSwitcherClicked(view: View) {
+ private fun onUserSwitcherClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showUserSwitcher(view)
+ footerActionsInteractor.showUserSwitcher(context, expandable)
}
- // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
- // or activity.
- private fun onSettingsButtonClicked(view: View) {
+ private fun onSettingsButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showSettings(Expandable.fromView(view))
+ footerActionsInteractor.showSettings(expandable)
}
- private fun onPowerButtonClicked(view: View) {
+ private fun onPowerButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, view)
+ footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
}
private fun userSwitcherButton(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
new file mode 100644
index 000000000000..2a61033cb302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.qs;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto";
+
+option java_multiple_files = true;
+
+message QsTileState {
+ oneof identifier {
+ string spec = 1;
+ com.android.systemui.util.ComponentNameProto component_name = 2;
+ }
+
+ enum State {
+ UNAVAILABLE = 0;
+ INACTIVE = 1;
+ ACTIVE = 2;
+ }
+
+ State state = 3;
+ oneof optional_boolean_state {
+ bool boolean_state = 4;
+ }
+ oneof optional_label {
+ string label = 5;
+ }
+ oneof optional_secondary_label {
+ string secondary_label = 6;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index bdcc6b0b2a57..314252bf310b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -23,13 +23,13 @@ import android.content.DialogInterface.BUTTON_NEUTRAL
import android.content.Intent
import android.provider.Settings
import android.view.LayoutInflater
-import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -77,10 +77,10 @@ class UserSwitchDialogController @VisibleForTesting constructor(
* Show a [UserDialog].
*
* Populate the dialog with information from and adapter obtained from
- * [userDetailViewAdapterProvider] and show it as launched from [view].
+ * [userDetailViewAdapterProvider] and show it as launched from [expandable].
*/
- fun showDialog(view: View) {
- with(dialogFactory(view.context)) {
+ fun showDialog(context: Context, expandable: Expandable) {
+ with(dialogFactory(context)) {
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
@@ -112,13 +112,19 @@ class UserSwitchDialogController @VisibleForTesting constructor(
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
- dialogLaunchAnimator.showFromView(
- this, view,
- cuj = DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
+ val controller =
+ expandable.dialogLaunchController(
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
)
- )
+ if (controller != null) {
+ dialogLaunchAnimator.show(
+ this,
+ controller,
+ )
+ } else {
+ show()
+ }
+
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fc93751b7f91..42e87530339d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -151,6 +151,8 @@ import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
@@ -583,6 +585,7 @@ public final class NotificationPanelViewController {
private final SysUiState mSysUiState;
private final NotificationShadeDepthController mDepthController;
+ private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
private KeyguardIndicationController mKeyguardIndicationController;
@@ -861,6 +864,7 @@ public final class NotificationPanelViewController {
PrivacyDotViewController privacyDotViewController,
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
+ NavigationBarController navigationBarController,
FragmentService fragmentService,
ContentResolver contentResolver,
RecordingController recordingController,
@@ -954,6 +958,7 @@ public final class NotificationPanelViewController {
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
+ mNavigationBarController = navigationBarController;
mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -1443,6 +1448,16 @@ public final class NotificationPanelViewController {
mMaxAllowedKeyguardNotifications = maxAllowed;
}
+ @VisibleForTesting
+ boolean getClosing() {
+ return mClosing;
+ }
+
+ @VisibleForTesting
+ boolean getIsFlinging() {
+ return mIsFlinging;
+ }
+
private void updateMaxDisplayedNotifications(boolean recompute) {
if (recompute) {
setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1));
@@ -2671,12 +2686,16 @@ public final class NotificationPanelViewController {
mQsExpanded = expanded;
updateQsState();
updateExpandedHeightToMaxHeight();
- mFalsingCollector.setQsExpanded(expanded);
- mCentralSurfaces.setQsExpanded(expanded);
- mNotificationsQSContainerController.setQsExpanded(expanded);
- mPulseExpansionHandler.setQsExpanded(expanded);
- mKeyguardBypassController.setQSExpanded(expanded);
- mPrivacyDotViewController.setQsExpanded(expanded);
+ setStatusAccessibilityImportance(expanded
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ updateSystemUiStateFlags();
+ NavigationBarView navigationBarView =
+ mNavigationBarController.getNavigationBarView(mDisplayId);
+ if (navigationBarView != null) {
+ navigationBarView.onStatusBarPanelStateChanged();
+ }
+ mShadeExpansionStateManager.onQsExpansionChanged(expanded);
}
}
@@ -3718,6 +3737,11 @@ public final class NotificationPanelViewController {
setListening(true);
}
+ @VisibleForTesting
+ void setTouchSlopExceeded(boolean isTouchSlopExceeded) {
+ mTouchSlopExceeded = isTouchSlopExceeded;
+ }
+
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
@@ -3909,20 +3933,19 @@ public final class NotificationPanelViewController {
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
- mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
- if (!mUpdateMonitor.isFaceDetectionRunning()) {
- startUnlockHintAnimation();
- }
- if (mUpdateMonitor.isFaceEnrolled()) {
+ if (didFaceAuthRun) {
mUpdateMonitor.requestActiveUnlock(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
"lockScreenEmptySpaceTap");
+ } else {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ mLockscreenGestureLogger
+ .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
+ startUnlockHintAnimation();
}
}
return true;
@@ -4776,6 +4799,7 @@ public final class NotificationPanelViewController {
mAmbientState.setSwipingUp(false);
if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
|| Math.abs(y - mInitialExpandY) > mTouchSlop
+ || (!isFullyExpanded() && !isFullyCollapsed())
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
mVelocityTracker.computeCurrentVelocity(1000);
float vel = mVelocityTracker.getYVelocity();
@@ -5173,7 +5197,8 @@ public final class NotificationPanelViewController {
*/
public void updatePanelExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(),
+ mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 1d9210592b78..66a22f4ddc0d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -135,7 +135,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
DumpManager dumpManager,
KeyguardStateController keyguardStateController,
ScreenOffAnimationController screenOffAnimationController,
- AuthController authController) {
+ AuthController authController,
+ ShadeExpansionStateManager shadeExpansionStateManager) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -156,6 +157,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
+ shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
@@ -607,8 +609,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
apply(mCurrentState);
}
- @Override
- public void setQsExpanded(boolean expanded) {
+ private void onQsExpansionChanged(Boolean expanded) {
mCurrentState.mQsExpanded = expanded;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index d6f0de83ecc1..73c6d507f035 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -36,17 +36,12 @@ class NotificationsQSContainerController @Inject constructor(
private val navigationModeController: NavigationModeController,
private val overviewProxyService: OverviewProxyService,
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val featureFlags: FeatureFlags,
@Main private val delayableExecutor: DelayableExecutor
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
- var qsExpanded = false
- set(value) {
- if (field != value) {
- field = value
- mView.invalidate()
- }
- }
+ private var qsExpanded = false
private var splitShadeEnabled = false
private var isQSDetailShowing = false
private var isQSCustomizing = false
@@ -71,6 +66,13 @@ class NotificationsQSContainerController @Inject constructor(
taskbarVisible = visible
}
}
+ private val shadeQsExpansionListener: ShadeQsExpansionListener =
+ ShadeQsExpansionListener { isQsExpanded ->
+ if (qsExpanded != isQsExpanded) {
+ qsExpanded = isQsExpanded
+ mView.invalidate()
+ }
+ }
// With certain configuration changes (like light/dark changes), the nav bar will disappear
// for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
@@ -106,6 +108,7 @@ class NotificationsQSContainerController @Inject constructor(
public override fun onViewAttached() {
updateResources()
overviewProxyService.addCallback(taskbarVisibilityListener)
+ shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener)
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
@@ -113,6 +116,7 @@ class NotificationsQSContainerController @Inject constructor(
override fun onViewDetached() {
overviewProxyService.removeCallback(taskbarVisibilityListener)
+ shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener)
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index f617d471351e..7bba74a8b125 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -21,6 +21,7 @@ import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.Compile
+import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
/**
@@ -31,12 +32,14 @@ import javax.inject.Inject
@SysUISingleton
class ShadeExpansionStateManager @Inject constructor() {
- private val expansionListeners = mutableListOf<ShadeExpansionListener>()
- private val stateListeners = mutableListOf<ShadeStateListener>()
+ private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
+ private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
+ private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
+ private var qsExpanded: Boolean = false
private var tracking: Boolean = false
private var dragDownPxAmount: Float = 0f
@@ -57,6 +60,15 @@ class ShadeExpansionStateManager @Inject constructor() {
expansionListeners.remove(listener)
}
+ fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
+ qsExpansionListeners.add(listener)
+ listener.onQsExpansionChanged(qsExpanded)
+ }
+
+ fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
+ qsExpansionListeners.remove(listener)
+ }
+
/** Adds a listener that will be notified when the panel state has changed. */
fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
@@ -126,6 +138,14 @@ class ShadeExpansionStateManager @Inject constructor() {
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
+ /** Called when the quick settings expansion changes to fully expanded or collapsed. */
+ fun onQsExpansionChanged(qsExpanded: Boolean) {
+ this.qsExpanded = qsExpanded
+
+ debugLog("qsExpanded=$qsExpanded")
+ qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
+ }
+
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
new file mode 100644
index 000000000000..14882b9afd2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.shade
+
+/** A listener interface to be notified of expansion events for the quick settings panel. */
+fun interface ShadeQsExpansionListener {
+ /**
+ * Invoked whenever the quick settings expansion changes, when it is fully collapsed or expanded
+ */
+ fun onQsExpansionChanged(isQsExpanded: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
new file mode 100644
index 000000000000..09019a69df47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.shade.data.repository
+
+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.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Business logic for shade interactions */
+@SysUISingleton
+class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
+
+ val shadeModel: Flow<ShadeModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : ShadeExpansionListener {
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+ // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
+ // It is too noisy and produces extra events that consumers won't care
+ // about
+ val info =
+ ShadeModel(
+ expansionAmount = event.fraction,
+ isExpanded = event.expanded,
+ isUserDragging = event.tracking
+ )
+ trySendWithFailureLogging(info, TAG, "updated shade expansion info")
+ }
+ }
+
+ shadeExpansionStateManager.addExpansionListener(callback)
+ trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info")
+
+ awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ private const val TAG = "ShadeRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
new file mode 100644
index 000000000000..ce0f4283ff83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.shade.domain.model
+
+import android.annotation.FloatRange
+
+/** Information about shade (NotificationPanel) expansion */
+data class ShadeModel(
+ /** 0 when collapsed, 1 when fully expanded. */
+ @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
+ /** Whether the panel should be considered expanded */
+ val isExpanded: Boolean = false,
+ /** Whether the user is actively dragging the panel. */
+ val isUserDragging: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 9d4a27c64e5b..4ae0f6a4a6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -67,12 +67,15 @@ import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.GcUtils;
import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dump.DumpHandler;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.tracing.ProtoTracer;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -182,6 +185,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private ProtoTracer mProtoTracer;
private final @Nullable CommandRegistry mRegistry;
+ private final @Nullable DumpHandler mDumpHandler;
/**
* These methods are called back on the main thread.
@@ -471,12 +475,18 @@ public class CommandQueue extends IStatusBar.Stub implements
}
public CommandQueue(Context context) {
- this(context, null, null);
+ this(context, null, null, null);
}
- public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
+ public CommandQueue(
+ Context context,
+ ProtoTracer protoTracer,
+ CommandRegistry registry,
+ DumpHandler dumpHandler
+ ) {
mProtoTracer = protoTracer;
mRegistry = registry;
+ mDumpHandler = dumpHandler;
context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
// We always have default display.
setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -1175,6 +1185,35 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
+ public void dumpProto(String[] args, ParcelFileDescriptor pfd) {
+ final FileDescriptor fd = pfd.getFileDescriptor();
+ // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+ // to just throw this work onto the handler just like the other messages
+ Thread thr = new Thread("Sysui.dumpProto") {
+ public void run() {
+ try {
+ if (mDumpHandler == null) {
+ return;
+ }
+ // We won't be using the PrintWriter.
+ OutputStream o = new OutputStream() {
+ @Override
+ public void write(int b) {}
+ };
+ mDumpHandler.dump(fd, new PrintWriter(o), args);
+ } finally {
+ try {
+ // Close the file descriptor so the TransferPipe finishes its thread
+ pfd.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+ thr.start();
+ }
+
+ @Override
public void runGcForTest() {
// Gc sysui
GcUtils.runGcAndFinalizersSync();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 0c9e1ec1ff77..e21acb7e0f68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -92,9 +92,6 @@ public interface NotificationShadeWindowController extends RemoteInputController
/** Sets the state of whether the keyguard is fading away or not. */
default void setKeyguardFadingAway(boolean keyguardFadingAway) {}
- /** Sets the state of whether the quick settings is expanded or not. */
- default void setQsExpanded(boolean expanded) {}
-
/** Sets the state of whether the user activities are forced or not. */
default void setForceUserActivity(boolean forceUserActivity) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 8222c9d9ba59..c630feba1dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -68,6 +69,7 @@ constructor(
configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
private val falsingManager: FalsingManager,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
private val falsingCollector: FalsingCollector,
dumpManager: DumpManager
@@ -126,6 +128,13 @@ constructor(
initResources(context)
}
})
+
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ if (qsExpanded != isQsExpanded) {
+ qsExpanded = isQsExpanded
+ }
+ }
+
mPowerManager = context.getSystemService(PowerManager::class.java)
dumpManager.registerDumpable(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 11e3d1773c4c..f574be056109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -29,6 +29,7 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -181,8 +182,10 @@ public interface CentralSurfacesDependenciesModule {
static CommandQueue provideCommandQueue(
Context context,
ProtoTracer protoTracer,
- CommandRegistry registry) {
- return new CommandQueue(context, protoTracer, registry);
+ CommandRegistry registry,
+ DumpHandler dumpHandler
+ ) {
+ return new CommandQueue(context, protoTracer, registry, dumpHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index d88f07ca304c..737b4812d4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -25,11 +25,12 @@ import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import com.android.internal.annotations.GuardedBy
-import com.android.systemui.animation.Interpolators
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
@@ -42,7 +43,6 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
-
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -67,7 +67,8 @@ class PrivacyDotViewController @Inject constructor(
private val stateController: StatusBarStateController,
private val configurationController: ConfigurationController,
private val contentInsetsProvider: StatusBarContentInsetsProvider,
- private val animationScheduler: SystemStatusAnimationScheduler
+ private val animationScheduler: SystemStatusAnimationScheduler,
+ shadeExpansionStateManager: ShadeExpansionStateManager
) {
private lateinit var tl: View
private lateinit var tr: View
@@ -128,6 +129,13 @@ class PrivacyDotViewController @Inject constructor(
updateStatusBarState()
}
})
+
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ dlog("setQsExpanded $isQsExpanded")
+ synchronized(lock) {
+ nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+ }
+ }
}
fun setUiExecutor(e: DelayableExecutor) {
@@ -138,13 +146,6 @@ class PrivacyDotViewController @Inject constructor(
showingListener = l
}
- fun setQsExpanded(expanded: Boolean) {
- dlog("setQsExpanded $expanded")
- synchronized(lock) {
- nextViewState = nextViewState.copy(qsExpanded = expanded)
- }
- }
-
@UiThread
fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 36b8333688ae..2734511de78c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -28,12 +28,7 @@ class NotifPipelineFlags @Inject constructor(
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
- fun isSmartspaceDedupingEnabled(): Boolean =
- featureFlags.isEnabled(Flags.SMARTSPACE) &&
- featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
-
- fun removeUnrankedNotifs(): Boolean =
- featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+ fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE)
fun fullScreenIntentRequiresKeyguard(): Boolean =
featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
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 7242506f1015..d97b712df030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.notification
import android.animation.ObjectAnimator
import android.util.FloatProperty
+import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
@@ -32,17 +34,20 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.min
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
+ dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
+ Dumpable {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -60,6 +65,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
+ private var mDozeAmountSource: String = "init"
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -142,6 +148,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
init {
+ dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
addListener(object : WakeUpListener {
@@ -248,13 +255,14 @@ class NotificationWakeUpCoordinator @Inject constructor(
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
- setDozeAmount(linear, eased)
+ setDozeAmount(linear, eased, source = "StatusBar")
}
- fun setDozeAmount(linear: Float, eased: Float) {
+ fun setDozeAmount(linear: Float, eased: Float, source: String) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
+ mDozeAmountSource = source
mStackScrollerController.setDozeAmount(mDozeAmount)
updateHideAmount()
if (changed && linear == 0.0f) {
@@ -271,7 +279,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f)
+ setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
}
if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -311,12 +319,11 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
- var amount = 1.0f
- if (statusBarStateController.state == StatusBarState.SHADE ||
- statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
- amount = 0.0f
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+ } else {
+ setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
- setDozeAmount(amount, amount)
return true
}
return false
@@ -332,7 +339,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f)
+ setDozeAmount(1f, 1f, source = "Override: animating screen off")
return true
}
@@ -414,6 +421,26 @@ class NotificationWakeUpCoordinator @Inject constructor(
private fun shouldAnimateVisibility() =
dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("mLinearDozeAmount: $mLinearDozeAmount")
+ pw.println("mDozeAmount: $mDozeAmount")
+ pw.println("mDozeAmountSource: $mDozeAmountSource")
+ pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
+ pw.println("mNotificationsVisible: $mNotificationsVisible")
+ pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+ pw.println("mVisibilityAmount: $mVisibilityAmount")
+ pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
+ pw.println("pulseExpanding: $pulseExpanding")
+ pw.println("state: ${StatusBarState.toString(state)}")
+ pw.println("fullyAwake: $fullyAwake")
+ pw.println("wakingUp: $wakingUp")
+ pw.println("willWakeUp: $willWakeUp")
+ pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
+ pw.println("pulsing: $pulsing")
+ pw.println("notificationsFullyHidden: $notificationsFullyHidden")
+ pw.println("canShowPulsingHuns: $canShowPulsingHuns")
+ }
+
interface WakeUpListener {
/**
* Called whenever the notifications are fully hidden or shown
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2887f975d46c..df35c9e6832a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -602,7 +602,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
mInconsistencyTracker.logNewMissingNotifications(rankingMap);
mInconsistencyTracker.logNewInconsistentRankings(currentEntriesWithoutRankings, rankingMap);
- if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
+ if (currentEntriesWithoutRankings != null) {
for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
entry.mCancellationReason = REASON_UNKNOWN;
tryRemoveNotification(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8f3eb4f7e223..8a31ed9271ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.util.ArrayMap
+import android.util.ArraySet
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -70,6 +72,7 @@ class HeadsUpCoordinator @Inject constructor(
@Main private val mExecutor: DelayableExecutor,
) : Coordinator {
private val mEntriesBindingUntil = ArrayMap<String, Long>()
+ private val mEntriesUpdateTimes = ArrayMap<String, Long>()
private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
private lateinit var mNotifPipeline: NotifPipeline
private var mNow: Long = -1
@@ -264,6 +267,9 @@ class HeadsUpCoordinator @Inject constructor(
}
// After this method runs, all posted entries should have been handled (or skipped).
mPostedEntries.clear()
+
+ // Also take this opportunity to clean up any stale entry update times
+ cleanUpEntryUpdateTimes()
}
/**
@@ -378,6 +384,9 @@ class HeadsUpCoordinator @Inject constructor(
isAlerting = false,
isBinding = false,
)
+
+ // Record the last updated time for this key
+ setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
/**
@@ -419,6 +428,9 @@ class HeadsUpCoordinator @Inject constructor(
cancelHeadsUpBind(posted.entry)
}
}
+
+ // Update last updated time for this entry
+ setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
/**
@@ -426,6 +438,7 @@ class HeadsUpCoordinator @Inject constructor(
*/
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
mPostedEntries.remove(entry.key)
+ mEntriesUpdateTimes.remove(entry.key)
cancelHeadsUpBind(entry)
val entryKey = entry.key
if (mHeadsUpManager.isAlerting(entryKey)) {
@@ -454,7 +467,12 @@ class HeadsUpCoordinator @Inject constructor(
// never) in mPostedEntries to need to alert, we need to check every notification
// known to the pipeline.
for (entry in mNotifPipeline.allNotifs) {
- // The only entries we can consider alerting for here are entries that have never
+ // Only consider entries that are recent enough, since we want to apply a fairly
+ // strict threshold for when an entry should be updated via only ranking and not an
+ // app-provided notification update.
+ if (!isNewEnoughForRankingUpdate(entry)) continue
+
+ // The only entries we consider alerting for here are entries that have never
// interrupted and that now say they should heads up; if they've alerted in the
// past, we don't want to incorrectly alert a second time if there wasn't an
// explicit notification update.
@@ -486,6 +504,41 @@ class HeadsUpCoordinator @Inject constructor(
(entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
}
+ /**
+ * Sets the updated time for the given entry to the specified time.
+ */
+ @VisibleForTesting
+ fun setUpdateTime(entry: NotificationEntry, time: Long) {
+ mEntriesUpdateTimes[entry.key] = time
+ }
+
+ /**
+ * Checks whether the entry is new enough to be updated via ranking update.
+ * We want to avoid updating an entry too long after it was originally posted/updated when we're
+ * only reacting to a ranking change, as relevant ranking updates are expected to come in
+ * fairly soon after the posting of a notification.
+ */
+ private fun isNewEnoughForRankingUpdate(entry: NotificationEntry): Boolean {
+ // If we don't have an update time for this key, default to "too old"
+ if (!mEntriesUpdateTimes.containsKey(entry.key)) return false
+
+ val updateTime = mEntriesUpdateTimes[entry.key] ?: return false
+ return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
+ }
+
+ private fun cleanUpEntryUpdateTimes() {
+ // Because we won't update entries that are older than this amount of time anyway, clean
+ // up any entries that are too old to notify.
+ val toRemove = ArraySet<String>()
+ for ((key, updateTime) in mEntriesUpdateTimes) {
+ if (updateTime == null ||
+ (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+ toRemove.add(key)
+ }
+ }
+ mEntriesUpdateTimes.removeAll(toRemove)
+ }
+
/** When an action is pressed on a notification, end HeadsUp lifetime extension. */
private val mActionPressListener = Consumer<NotificationEntry> { entry ->
if (mNotifsExtendingLifetime.contains(entry)) {
@@ -597,6 +650,9 @@ class HeadsUpCoordinator @Inject constructor(
companion object {
private const val TAG = "HeadsUpCoordinator"
private const val BIND_TIMEOUT = 1000L
+
+ // This value is set to match MAX_SOUND_DELAY_MS in NotificationRecord.
+ private const val MAX_RANKING_UPDATE_DELAY_MS: Long = 2000
}
data class PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6e76691ae1b1..d2db6224ef52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -407,7 +407,10 @@ public class PreparationCoordinator implements Coordinator {
mLogger.logGroupInflationTookTooLong(group);
return false;
}
- if (mInflatingNotifs.contains(group.getSummary())) {
+ // Only delay release if the summary is not inflated.
+ // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been
+ // done by this point, we can revert back to checking for mInflatingNotifs.contains(...)
+ if (group.getSummary() != null && !isInflated(group.getSummary())) {
mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index c5a69217a1ac..c4f5a3a30608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.interruption;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
import android.app.NotificationManager;
import android.content.ContentResolver;
@@ -32,6 +34,8 @@ import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -68,10 +72,30 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
private final NotificationInterruptLogger mLogger;
private final NotifPipelineFlags mFlags;
private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ private final UiEventLogger mUiEventLogger;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
+ public enum NotificationInterruptEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
+ FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
+
+ @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
+ FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+
+ private final int mId;
+
+ NotificationInterruptEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
@Inject
public NotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
@@ -85,7 +109,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
NotificationInterruptLogger logger,
@Main Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
mDreamManager = dreamManager;
@@ -97,6 +122,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mLogger = logger;
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
+ mUiEventLogger = uiEventLogger;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -203,7 +229,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// b/231322873: Detect and report an event when a notification has both an FSI and a
// suppressive groupAlertBehavior, and now correctly block the FSI from firing.
final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+ mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
return false;
}
@@ -249,7 +277,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// Detect the case determined by b/231322873 to launch FSI while device is in use,
// as blocked by the correct implementation, and report the event.
final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+ mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 832a739a9080..0380fff1e2af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -20,8 +20,9 @@ package com.android.systemui.statusbar.notification.logging
/** Describes usage of a notification. */
data class NotificationMemoryUsage(
val packageName: String,
- val notificationId: String,
+ val notificationKey: String,
val objectUsage: NotificationObjectUsage,
+ val viewUsage: List<NotificationViewUsage>
)
/**
@@ -39,3 +40,26 @@ data class NotificationObjectUsage(
val extender: Int,
val hasCustomView: Boolean,
)
+
+enum class ViewType {
+ PUBLIC_VIEW,
+ PRIVATE_CONTRACTED_VIEW,
+ PRIVATE_EXPANDED_VIEW,
+ PRIVATE_HEADS_UP_VIEW,
+ TOTAL
+}
+
+/**
+ * Describes current memory of a notification view hierarchy.
+ *
+ * The values are in bytes.
+ */
+data class NotificationViewUsage(
+ val viewType: ViewType,
+ val smallIcon: Int,
+ val largeIcon: Int,
+ val systemIcons: Int,
+ val style: Int,
+ val customViews: Int,
+ val softwareBitmapsPenalty: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
new file mode 100644
index 000000000000..7d39e18ab349
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -0,0 +1,212 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.annotation.WorkerThread
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Calculates estimated memory usage of [Notification] and [NotificationEntry] objects. */
+internal object NotificationMemoryMeter {
+
+ private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+ private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+ private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+
+ /** Returns a list of memory use entries for currently shown notifications. */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notifications: Collection<NotificationEntry>,
+ ): List<NotificationMemoryUsage> {
+ return notifications
+ .asSequence()
+ .map { entry ->
+ val packageName = entry.sbn.packageName
+ val notificationObjectUsage =
+ notificationMemoryUse(entry.sbn.notification, hashSetOf())
+ val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
+ NotificationMemoryUsage(
+ packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationObjectUsage,
+ notificationViewUsage
+ )
+ }
+ .toList()
+ }
+
+ @WorkerThread
+ fun notificationMemoryUse(
+ entry: NotificationEntry,
+ seenBitmaps: HashSet<Int> = hashSetOf(),
+ ): NotificationMemoryUsage {
+ return NotificationMemoryUsage(
+ entry.sbn.packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationMemoryUse(entry.sbn.notification, seenBitmaps),
+ NotificationMemoryViewWalker.getViewUsage(entry.row)
+ )
+ }
+
+ /**
+ * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+ * inspect Bitmaps in the object and provide summary of memory usage.
+ */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notification: Notification,
+ seenBitmaps: HashSet<Int> = hashSetOf(),
+ ): NotificationObjectUsage {
+ val extras = notification.extras
+ val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+ val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+ // Collect memory usage of extra styles
+
+ // Big Picture
+ val bigPictureIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+ val bigPictureUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+ // People
+ val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+ val peopleUse =
+ peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+ // Calling
+ val callingPersonUse =
+ computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+ val verificationIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+ // Messages
+ val messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ )
+ val messagesUse =
+ messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+ val historicMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ )
+ val historyicMessagesUse =
+ historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+ // Extenders
+ val carExtender = extras.getBundle(CAR_EXTENSIONS)
+ val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+ val carExtenderIcon =
+ computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+ val tvExtender = extras.getBundle(TV_EXTENSIONS)
+ val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+ val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+ val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+ val wearExtenderBackground =
+ computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+ val style = notification.notificationStyle
+ val hasCustomView = notification.contentView != null || notification.bigContentView != null
+ val extrasSize = computeBundleSize(extras)
+
+ return NotificationObjectUsage(
+ smallIcon = smallIconUse,
+ largeIcon = largeIconUse,
+ extras = extrasSize,
+ style = style?.simpleName,
+ styleIcon =
+ bigPictureIconUse +
+ peopleUse +
+ callingPersonUse +
+ verificationIconUse +
+ messagesUse +
+ historyicMessagesUse,
+ bigPicture = bigPictureUse,
+ extender =
+ carExtenderSize +
+ carExtenderIcon +
+ tvExtenderSize +
+ wearExtenderSize +
+ wearExtenderBackground,
+ hasCustomView = hasCustomView
+ )
+ }
+
+ /**
+ * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+ * bitmaps). Can be slow.
+ */
+ private fun computeBundleSize(extras: Bundle): Int {
+ val parcel = Parcel.obtain()
+ try {
+ extras.writeToParcel(parcel, 0)
+ return parcel.dataSize()
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ /**
+ * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+ * if the key does not exist in extras.
+ */
+ private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+ return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+ is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+ is Icon -> computeIconUse(parcelable, seenBitmaps)
+ is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+ else -> 0
+ }
+ }
+
+ /**
+ * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+ * defined via Uri or a resource.
+ *
+ * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+ */
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ when (icon?.type) {
+ Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+ else -> 0
+ }
+
+ /**
+ * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+ * seenBitmaps set, this method returns 0 to avoid double counting.
+ *
+ * @return memory usage of the bitmap in bytes
+ */
+ private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+ val refId = System.identityHashCode(bitmap)
+ if (seenBitmaps?.contains(refId) == true) {
+ return 0
+ }
+
+ seenBitmaps?.add(refId)
+ return bitmap.allocationByteCount
+ }
+
+ private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+ val refId = System.identityHashCode(icon.dataBytes)
+ if (seenBitmaps.contains(refId)) {
+ return 0
+ }
+
+ seenBitmaps.add(refId)
+ return icon.dataLength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index 958978ecd858..c09cc4306ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -17,22 +17,11 @@
package com.android.systemui.statusbar.notification.logging
-import android.app.Notification
-import android.app.Person
-import android.graphics.Bitmap
-import android.graphics.drawable.Icon
-import android.os.Bundle
-import android.os.Parcel
-import android.os.Parcelable
import android.util.Log
-import androidx.annotation.WorkerThread
-import androidx.core.util.contains
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
import java.io.PrintWriter
import javax.inject.Inject
@@ -46,12 +35,7 @@ constructor(
) : Dumpable {
companion object {
- private const val TAG = "NotificationMemMonitor"
- private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
- private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
- private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
- private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
- private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ private const val TAG = "NotificationMemory"
}
fun init() {
@@ -60,184 +44,123 @@ constructor(
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
- currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+ .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+ dumpNotificationObjects(pw, memoryUse)
+ dumpNotificationViewUsage(pw, memoryUse)
}
- @WorkerThread
- fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
- return notificationMemoryUse(notificationPipeline.allNotifs)
- }
-
- /** Returns a list of memory use entries for currently shown notifications. */
- @WorkerThread
- fun notificationMemoryUse(
- notifications: Collection<NotificationEntry>
- ): List<NotificationMemoryUsage> {
- return notifications
- .asSequence()
- .map { entry ->
- val packageName = entry.sbn.packageName
- val notificationObjectUsage =
- computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
- NotificationMemoryUsage(
- packageName,
- NotificationUtils.logKey(entry.sbn.key),
- notificationObjectUsage
- )
- }
- .toList()
- }
-
- /**
- * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
- * inspect Bitmaps in the object and provide summary of memory usage.
- */
- private fun computeNotificationObjectUse(
- notification: Notification,
- seenBitmaps: HashSet<Int>
- ): NotificationObjectUsage {
- val extras = notification.extras
- val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
- val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
-
- // Collect memory usage of extra styles
-
- // Big Picture
- val bigPictureIconUse =
- computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
- computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
- val bigPictureUse =
- computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
- computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
-
- // People
- val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
- val peopleUse =
- peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
-
- // Calling
- val callingPersonUse =
- computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
- val verificationIconUse =
- computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
-
- // Messages
- val messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- extras.getParcelableArray(Notification.EXTRA_MESSAGES)
- )
- val messagesUse =
- messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
- val historicMessages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ /** Renders a table of notification object usage into passed [PrintWriter]. */
+ private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+ pw.println("Notification Object Usage")
+ pw.println("-----------")
+ pw.println(
+ "Package".padEnd(35) +
+ "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
+ )
+ pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+ pw.println()
+
+ memoryUse.forEach { use ->
+ pw.println(
+ use.packageName.padEnd(35) +
+ "\t\t" +
+ "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+ (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
+ "\t\t${use.objectUsage.styleIcon}\t" +
+ "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+ "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+ use.notificationKey
)
- val historyicMessagesUse =
- historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
-
- // Extenders
- val carExtender = extras.getBundle(CAR_EXTENSIONS)
- val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
- val carExtenderIcon =
- computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
-
- val tvExtender = extras.getBundle(TV_EXTENSIONS)
- val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
-
- val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
- val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
- val wearExtenderBackground =
- computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
-
- val style = notification.notificationStyle
- val hasCustomView = notification.contentView != null || notification.bigContentView != null
- val extrasSize = computeBundleSize(extras)
-
- return NotificationObjectUsage(
- smallIconUse,
- largeIconUse,
- extrasSize,
- style?.simpleName,
- bigPictureIconUse +
- peopleUse +
- callingPersonUse +
- verificationIconUse +
- messagesUse +
- historyicMessagesUse,
- bigPictureUse,
- carExtenderSize +
- carExtenderIcon +
- tvExtenderSize +
- wearExtenderSize +
- wearExtenderBackground,
- hasCustomView
+ }
+
+ // Calculate totals for easily glanceable summary.
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var styleIcon: Int = 0,
+ var bigPicture: Int = 0,
+ var extender: Int = 0,
+ var extras: Int = 0,
)
- }
- /**
- * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
- * bitmaps). Can be slow.
- */
- private fun computeBundleSize(extras: Bundle): Int {
- val parcel = Parcel.obtain()
- try {
- extras.writeToParcel(parcel, 0)
- return parcel.dataSize()
- } finally {
- parcel.recycle()
- }
- }
+ val totals =
+ memoryUse.fold(Totals()) { t, usage ->
+ t.smallIcon += usage.objectUsage.smallIcon
+ t.largeIcon += usage.objectUsage.largeIcon
+ t.styleIcon += usage.objectUsage.styleIcon
+ t.bigPicture += usage.objectUsage.bigPicture
+ t.extender += usage.objectUsage.extender
+ t.extras += usage.objectUsage.extras
+ t
+ }
- /**
- * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
- * if the key does not exist in extras.
- */
- private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
- return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
- is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
- is Icon -> computeIconUse(parcelable, seenBitmaps)
- is Person -> computeIconUse(parcelable.icon, seenBitmaps)
- else -> 0
- }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ "".padEnd(35) +
+ "\t\t" +
+ "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+ "".padEnd(15) +
+ "\t\t${toKb(totals.styleIcon)}\t" +
+ "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+ toKb(totals.extras)
+ )
+ pw.println()
}
- /**
- * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
- * defined via Uri or a resource.
- *
- * @return memory usage in bytes or 0 if the icon is Uri/Resource based
- */
- private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
- when (icon?.type) {
- Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
- Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
- Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
- else -> 0
- }
-
- /**
- * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
- * seenBitmaps set, this method returns 0 to avoid double counting.
- *
- * @return memory usage of the bitmap in bytes
- */
- private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
- val refId = System.identityHashCode(bitmap)
- if (seenBitmaps?.contains(refId) == true) {
- return 0
- }
+ /** Renders a table of notification view usage into passed [PrintWriter] */
+ private fun dumpNotificationViewUsage(
+ pw: PrintWriter,
+ memoryUse: List<NotificationMemoryUsage>,
+ ) {
+
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var style: Int = 0,
+ var customViews: Int = 0,
+ var softwareBitmapsPenalty: Int = 0,
+ )
- seenBitmaps?.add(refId)
- return bitmap.allocationByteCount
+ val totals = Totals()
+ pw.println("Notification View Usage")
+ pw.println("-----------")
+ pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+ pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+ pw.println()
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .forEach { use ->
+ pw.println(use.packageName + " " + use.notificationKey)
+ use.viewUsage.forEach { view ->
+ pw.println(
+ " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+ "\t${view.largeIcon}\t${view.style}" +
+ "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+ )
+
+ if (view.viewType == ViewType.TOTAL) {
+ totals.smallIcon += view.smallIcon
+ totals.largeIcon += view.largeIcon
+ totals.style += view.style
+ totals.customViews += view.customViews
+ totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+ }
+ }
+ }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+ "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+ "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+ )
+ pw.println()
}
- private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
- val refId = System.identityHashCode(icon.dataBytes)
- if (seenBitmaps.contains(refId)) {
- return 0
- }
-
- seenBitmaps.add(refId)
- return icon.dataLength
+ private fun toKb(bytes: Int): String {
+ return (bytes / 1024).toString() + " KB"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
new file mode 100644
index 000000000000..a0bee1502f51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -0,0 +1,173 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.android.internal.R
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.util.children
+
+/** Walks view hiearchy of a given notification to estimate its memory use. */
+internal object NotificationMemoryViewWalker {
+
+ private const val TAG = "NotificationMemory"
+
+ /** Builder for [NotificationViewUsage] objects. */
+ private class UsageBuilder {
+ private var smallIcon: Int = 0
+ private var largeIcon: Int = 0
+ private var systemIcons: Int = 0
+ private var style: Int = 0
+ private var customViews: Int = 0
+ private var softwareBitmaps = 0
+
+ fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse }
+ fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse }
+ fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse }
+ fun addStyle(styleUse: Int) = apply { style += styleUse }
+ fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {
+ softwareBitmaps += softwareBitmapUse
+ }
+
+ fun addCustomViews(customViewsUse: Int) = apply { customViews += customViewsUse }
+
+ fun build(viewType: ViewType): NotificationViewUsage {
+ return NotificationViewUsage(
+ viewType = viewType,
+ smallIcon = smallIcon,
+ largeIcon = largeIcon,
+ systemIcons = systemIcons,
+ style = style,
+ customViews = customViews,
+ softwareBitmapsPenalty = softwareBitmaps,
+ )
+ }
+ }
+
+ /**
+ * Returns memory usage of public and private views contained in passed
+ * [ExpandableNotificationRow]
+ */
+ fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
+ if (row == null) {
+ return listOf()
+ }
+
+ // The ordering here is significant since it determines deduplication of seen drawables.
+ return listOf(
+ getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+ getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
+ getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+ getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
+ getTotalUsage(row)
+ )
+ }
+
+ /**
+ * Calculate total usage of all views - we need to do a separate traversal to make sure we don't
+ * double count fields.
+ */
+ private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
+ val totalUsage = UsageBuilder()
+ val seenObjects = hashSetOf<Int>()
+
+ row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
+ row.privateLayout?.let { child ->
+ for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
+ (view as? ViewGroup)?.let { v ->
+ computeViewHierarchyUse(v, totalUsage, seenObjects)
+ }
+ }
+ }
+ return totalUsage.build(ViewType.TOTAL)
+ }
+
+ private fun getViewUsage(
+ type: ViewType,
+ rootView: View?,
+ seenObjects: HashSet<Int> = hashSetOf()
+ ): NotificationViewUsage {
+ val usageBuilder = UsageBuilder()
+ (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
+ return usageBuilder.build(type)
+ }
+
+ private fun computeViewHierarchyUse(
+ rootView: ViewGroup,
+ builder: UsageBuilder,
+ seenObjects: HashSet<Int> = hashSetOf(),
+ ) {
+ for (child in rootView.children) {
+ if (child is ViewGroup) {
+ computeViewHierarchyUse(child, builder, seenObjects)
+ } else {
+ computeViewUse(child, builder, seenObjects)
+ }
+ }
+ }
+
+ private fun computeViewUse(view: View, builder: UsageBuilder, seenObjects: HashSet<Int>) {
+ if (view !is ImageView) return
+ val drawable = view.drawable ?: return
+ val drawableRef = System.identityHashCode(drawable)
+ if (seenObjects.contains(drawableRef)) return
+ val drawableUse = computeDrawableUse(drawable, seenObjects)
+ // TODO(b/235451049): We need to make sure we traverse large icon before small icon -
+ // sometimes the large icons are assigned to small icon views and we want to
+ // attribute them to large view in those cases.
+ when (view.id) {
+ R.id.left_icon,
+ R.id.icon,
+ R.id.conversation_icon -> builder.addSmallIcon(drawableUse)
+ R.id.right_icon -> builder.addLargeIcon(drawableUse)
+ R.id.big_picture -> builder.addStyle(drawableUse)
+ // Elements that are part of platform with resources
+ R.id.phishing_alert,
+ R.id.feedback,
+ R.id.alerted_icon,
+ R.id.expand_button_icon,
+ R.id.remote_input_send -> builder.addSystem(drawableUse)
+ // Custom view ImageViews
+ else -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Custom view: ${identifierForView(view)}")
+ }
+ builder.addCustomViews(drawableUse)
+ }
+ }
+
+ if (isDrawableSoftwareBitmap(drawable)) {
+ builder.addSoftwareBitmapPenalty(drawableUse)
+ }
+
+ seenObjects.add(drawableRef)
+ }
+
+ private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
+ when (drawable) {
+ is BitmapDrawable -> {
+ val ref = System.identityHashCode(drawable.bitmap)
+ if (seenObjects.contains(ref)) {
+ 0
+ } else {
+ seenObjects.add(ref)
+ drawable.bitmap.allocationByteCount
+ }
+ }
+ else -> 0
+ }
+
+ private fun isDrawableSoftwareBitmap(drawable: Drawable) =
+ drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+
+ private fun identifierForView(view: View) =
+ if (view.id == View.NO_ID) {
+ "no-id"
+ } else {
+ view.resources.getResourceName(view.id)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 25fd483efa2d..70cf56d6d12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -262,8 +262,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
@Override
void startActivity(Intent intent, boolean dismissShade, Callback callback);
- void setQsExpanded(boolean expanded);
-
boolean isWakeUpComingFromTouch();
boolean isFalsingThresholdNeeded();
@@ -455,6 +453,9 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void collapseShade();
+ /** Collapse the shade, but conditional on a flag specific to the trigger of a bugreport. */
+ void collapseShadeForBugreport();
+
int getWakefulnessState();
boolean isScreenFullyOff();
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 2c834cf781a6..29642beda53d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -868,6 +868,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
}
+ // Do not restart System UI when the bugreport flag changes.
+ mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> {
+ event.requestNoRestart();
+ });
+
mStatusBarSignalPolicy.init();
mKeyguardIndicationController.init();
@@ -1772,18 +1777,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
@Override
- public void setQsExpanded(boolean expanded) {
- mNotificationShadeWindowController.setQsExpanded(expanded);
- mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
- ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- mNotificationPanelViewController.updateSystemUiStateFlags();
- if (getNavigationBarView() != null) {
- getNavigationBarView().onStatusBarPanelStateChanged();
- }
- }
-
- @Override
public boolean isWakeUpComingFromTouch() {
return mWakeUpComingFromTouch;
}
@@ -3561,6 +3554,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
+ @Override
+ public void collapseShadeForBugreport() {
+ if (!mFeatureFlags.isEnabled(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT)) {
+ collapseShade();
+ }
+ }
+
@VisibleForTesting
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b987f6815000..b965ac97cc1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -26,6 +26,7 @@ import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -95,14 +96,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
var bouncerShowing: Boolean = false
var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
- var qSExpanded = false
- set(value) {
- val changed = field != value
- field = value
- if (changed && !value) {
- maybePerformPendingUnlock()
- }
- }
+ var qsExpanded = false
@Inject
constructor(
@@ -111,6 +105,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
statusBarStateController: StatusBarStateController,
lockscreenUserManager: NotificationLockscreenUserManager,
keyguardStateController: KeyguardStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
dumpManager: DumpManager
) {
this.mKeyguardStateController = keyguardStateController
@@ -132,6 +127,14 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
}
})
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ val changed = qsExpanded != isQsExpanded
+ qsExpanded = isQsExpanded
+ if (changed && !isQsExpanded) {
+ maybePerformPendingUnlock()
+ }
+ }
+
val dismissByDefault = if (context.resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable(object : TunerService.Tunable {
@@ -160,7 +163,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
): Boolean {
if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
val can = canBypass()
- if (!can && (isPulseExpanding || qSExpanded)) {
+ if (!can && (isPulseExpanding || qsExpanded)) {
pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
}
return can
@@ -189,7 +192,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
altBouncerShowing -> true
statusBarStateController.state != StatusBarState.KEYGUARD -> false
launchingAffordance -> false
- isPulseExpanding || qSExpanded -> false
+ isPulseExpanding || qsExpanded -> false
else -> true
}
}
@@ -214,7 +217,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
pw.println(" altBouncerShowing: $altBouncerShowing")
pw.println(" isPulseExpanding: $isPulseExpanding")
pw.println(" launchingAffordance: $launchingAffordance")
- pw.println(" qSExpanded: $qSExpanded")
+ pw.println(" qSExpanded: $qsExpanded")
pw.println(" hasFaceFeature: $hasFaceFeature")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 00c3e8fac0b4..5e2a7c8ca540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -26,6 +26,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
@@ -67,7 +68,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
ActivityLaunchAnimator.Controller.fromView(v, null),
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
} else {
- mUserSwitchDialogController.showDialog(v);
+ mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ece7ee0ec98a..86f6ff850409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -372,7 +372,7 @@ public interface StatusBarIconController {
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
- if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
// This starts the flow for the new pipeline, and will notify us of changes
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
@@ -451,7 +451,7 @@ public interface StatusBarIconController {
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
final BaseStatusBarFrameLayout view;
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
// correct view state so we don't need to call applyWifiState.
@@ -474,9 +474,9 @@ public interface StatusBarIconController {
String slot,
MobileIconState state
) {
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon while the new "
- + "pipeline is enabled is not supported");
+ + "icons are enabled is not supported");
}
// Use the `subId` field as a key to query for the correct context
@@ -497,7 +497,7 @@ public interface StatusBarIconController {
String slot,
int subId
) {
- if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ "pipeline, but the enabled flag is false.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index e106b9e327ef..31e960ad7d69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -224,9 +224,9 @@ public class StatusBarIconControllerImpl implements Tunable,
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring old pipeline callbacks, because the new "
- + "pipeline frontend is enabled");
+ + "icons are enabled");
return;
}
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
@@ -249,9 +249,9 @@ public class StatusBarIconControllerImpl implements Tunable,
@Override
public void setNewMobileIconSubIds(List<Integer> subIds) {
- if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring new pipeline callback, "
- + "since the frontend is disabled");
+ + "since the new icons are disabled");
return;
}
Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
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 5f5ec68ba898..5480f5d7489e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -60,7 +60,6 @@ import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
@@ -136,7 +135,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
private final BouncerCallbackInteractor mBouncerCallbackInteractor;
private final BouncerInteractor mBouncerInteractor;
- private final BouncerViewDelegate mBouncerViewDelegate;
+ private final BouncerView mBouncerView;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -327,7 +326,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardSecurityModel = keyguardSecurityModel;
mBouncerCallbackInteractor = bouncerCallbackInteractor;
mBouncerInteractor = bouncerInteractor;
- mBouncerViewDelegate = bouncerView.getDelegate();
+ mBouncerView = bouncerView;
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -804,7 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- if (dozing || mBouncer.needsFullscreenBouncer()
+ if (dozing || needsFullscreenBouncer()
|| mKeyguardStateController.isOccluded()) {
reset(dozing /* hideBouncerWhenShowing */);
}
@@ -1081,7 +1080,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* @return whether a back press can be handled right now.
*/
public boolean canHandleBackPressed() {
- return mBouncer.isShowing();
+ return bouncerIsShowing();
}
/**
@@ -1094,7 +1093,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
- if (bouncerIsScrimmed() && needsFullscreenBouncer()) {
+ if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
hideBouncer(false);
updateStates();
} else {
@@ -1124,8 +1123,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean isFullscreenBouncer() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.isFullScreenBouncer();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().isFullScreenBouncer();
}
return mBouncer != null && mBouncer.isFullscreenBouncer();
}
@@ -1284,15 +1283,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean shouldDismissOnMenuPressed() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.shouldDismissOnMenuPressed();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
}
return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.interceptMediaKey(event);
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().interceptMediaKey(event);
}
return mBouncer != null && mBouncer.interceptMediaKey(event);
}
@@ -1301,8 +1300,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
index 0d52f46e571f..e498ae451400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone.userswitcher
import android.content.Intent
import android.os.UserHandle
import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +76,7 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor(
null /* ActivityLaunchAnimator.Controller */,
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
} else {
- userSwitcherDialogController.showDialog(view)
+ userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 9b8b6434827e..06cd12dd1a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -24,29 +24,19 @@ import javax.inject.Inject
/** All flagging methods related to the new status bar pipeline (see b/238425913). */
@SysUISingleton
class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
- /**
- * Returns true if we should run the new pipeline backend.
- *
- * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs,
- * and logs the output state.
- */
- fun isNewPipelineBackendEnabled(): Boolean =
- featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND)
+ /** True if we should display the mobile icons using the new status bar data pipeline. */
+ fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
- /**
- * Returns true if we should run the new pipeline frontend *and* backend.
- *
- * The new pipeline frontend will use the outputted state from the new backend and will make the
- * correct changes to the UI.
- */
- fun isNewPipelineFrontendEnabled(): Boolean =
- isNewPipelineBackendEnabled() &&
- featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND)
+ /** True if we should display the wifi icon using the new status bar data pipeline. */
+ fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
+
+ // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
+ // logging without getting the UI effects.
/**
- * Returns true if we should apply some coloring to icons that were rendered with the new
+ * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
* pipeline to help with debugging.
*/
- // For now, just always apply the debug coloring if we've enabled frontend rendering.
- fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled()
+ // For now, just always apply the debug coloring if we've enabled the new icon.
+ fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
new file mode 100644
index 000000000000..7aa5ee1389f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Global
+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.qs.SettingObserver
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides data related to airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. It is
+ * only used to help [com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel]
+ * determine what parts of the wifi icon view should be shown.
+ *
+ * TODO(b/238425913): Consider migrating the status bar airplane mode icon to use this repo.
+ */
+interface AirplaneModeRepository {
+ /** Observable for whether the device is currently in airplane mode. */
+ val isAirplaneMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class AirplaneModeRepositoryImpl
+@Inject
+constructor(
+ @Background private val bgHandler: Handler,
+ private val globalSettings: GlobalSettings,
+ logger: ConnectivityPipelineLogger,
+ @Application scope: CoroutineScope,
+) : AirplaneModeRepository {
+ // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
+ override val isAirplaneMode: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val observer =
+ object :
+ SettingObserver(
+ globalSettings,
+ bgHandler,
+ Global.AIRPLANE_MODE_ON,
+ UserHandle.USER_ALL
+ ) {
+ override fun handleValueChanged(value: Int, observedChange: Boolean) {
+ trySend(value == 1)
+ }
+ }
+
+ observer.isListening = true
+ trySend(observer.value == 1)
+ awaitClose { observer.isListening = false }
+ }
+ .distinctUntilChanged()
+ .logInputChange(logger, "isAirplaneMode")
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ // When the observer starts listening, the flow will emit the current value so the
+ // initialValue here is irrelevant.
+ initialValue = false,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
new file mode 100644
index 000000000000..3e9b2c2ae809
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [AirplaneModeRepository] for more details.
+ */
+@SysUISingleton
+class AirplaneModeInteractor
+@Inject
+constructor(
+ airplaneModeRepository: AirplaneModeRepository,
+ connectivityRepository: ConnectivityRepository,
+) {
+ /** True if the device is currently in airplane mode. */
+ val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
+
+ /** True if we're configured to force-hide the airplane mode icon and false otherwise. */
+ val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.AIRPLANE) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
new file mode 100644
index 000000000000..fe30c0169021
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.statusbar.pipeline.airplane.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Models the UI state for the status bar airplane mode icon.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
+ * more details.
+ */
+@SysUISingleton
+class AirplaneModeViewModel
+@Inject
+constructor(
+ interactor: AirplaneModeInteractor,
+ logger: ConnectivityPipelineLogger,
+ @Application private val scope: CoroutineScope,
+) {
+ /** True if the airplane mode icon is currently visible in the status bar. */
+ val isAirplaneModeIconVisible: StateFlow<Boolean> =
+ combine(interactor.isAirplaneMode, interactor.isForceHidden) {
+ isAirplaneMode,
+ isAirplaneIconForceHidden ->
+ isAirplaneMode && !isAirplaneIconForceHidden
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "isAirplaneModeIconVisible")
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 06d554232565..2aaa085645e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -30,6 +32,9 @@ import dagger.Module
@Module
abstract class StatusBarPipelineModule {
@Binds
+ abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 681cf7254ae7..93448c1dee0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -39,7 +39,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
@@ -64,6 +63,9 @@ interface WifiRepository {
/** Observable for the current wifi enabled status. */
val isWifiEnabled: StateFlow<Boolean>
+ /** Observable for the current wifi default status. */
+ val isWifiDefault: StateFlow<Boolean>
+
/** Observable for the current wifi network. */
val wifiNetwork: StateFlow<WifiNetworkModel>
@@ -103,7 +105,7 @@ class WifiRepositoryImpl @Inject constructor(
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
- .logOutputChange(logger, "enabled")
+ .logInputChange(logger, "enabled")
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
@@ -111,6 +113,39 @@ class WifiRepositoryImpl @Inject constructor(
)
}
+ override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
+ // Note: This callback doesn't do any logging because we already log every network change
+ // in the [wifiNetwork] callback.
+ val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ // This method will always be called immediately after the network becomes the
+ // default, in addition to any time the capabilities change while the network is
+ // the default.
+ // If this network contains valid wifi info, then wifi is the default network.
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ trySend(wifiInfo != null)
+ }
+
+ override fun onLost(network: Network) {
+ // The system no longer has a default network, so wifi is definitely not default.
+ trySend(false)
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .logInputChange(logger, "isWifiDefault")
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 04b17ed2924a..3a3e611de96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -59,6 +59,9 @@ class WifiInteractor @Inject constructor(
/** Our current enabled status. */
val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+ /** Our current default status. */
+ val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 273be63eb8a2..25537b948517 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -91,6 +91,7 @@ object WifiViewBinder {
val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
+ val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
view.isVisible = true
iconView.isVisible = true
@@ -142,6 +143,12 @@ object WifiViewBinder {
activityContainerView.isVisible = visible
}
}
+
+ launch {
+ viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible ->
+ airplaneSpacer.isVisible = visible
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 40f948f9ee6c..95ab251422b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -32,6 +32,7 @@ class HomeWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -40,4 +41,5 @@ class HomeWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 9642ac42972e..86535d63f84f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -29,6 +29,7 @@ class KeyguardWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class KeyguardWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e23f8c7e97e0..7cbdf5dbdf2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -44,11 +44,14 @@ abstract class LocationBasedWifiViewModel(
/** True if the activity container view should be visible. */
val isActivityContainerVisible: Flow<Boolean>,
+
+ /** True if the airplane spacer view should be visible. */
+ val isAirplaneSpacerVisible: Flow<Boolean>,
) {
/** The color that should be used to tint the icon. */
val tint: Flow<Int> =
flowOf(
- if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+ if (statusBarPipelineFlags.useWifiDebugColoring()) {
debugTint
} else {
DEFAULT_TINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index 0ddf90e21872..fd54c5f5062e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -29,6 +29,7 @@ class QsWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class QsWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ebbd77b72014..89b96b7bc75d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
@@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.stateIn
class WifiViewModel
@Inject
constructor(
+ airplaneModeViewModel: AirplaneModeViewModel,
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
@@ -124,9 +126,10 @@ constructor(
private val wifiIcon: StateFlow<Icon.Resource?> =
combine(
interactor.isEnabled,
+ interactor.isDefault,
interactor.isForceHidden,
interactor.wifiNetwork,
- ) { isEnabled, isForceHidden, wifiNetwork ->
+ ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
return@combine null
}
@@ -135,6 +138,7 @@ constructor(
val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
return@combine when {
+ isDefault -> icon
wifiConstants.alwaysShowIconIfEnabled -> icon
!connectivityConstants.hasDataCapabilities -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
@@ -175,6 +179,12 @@ constructor(
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
+ // airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
+ // that appropriately knows about both icons and sets the padding appropriately.
+ private val isAirplaneSpacerVisible: Flow<Boolean> =
+ airplaneModeViewModel.isAirplaneModeIconVisible
+
/** A view model for the status bar on the home screen. */
val home: HomeWifiViewModel =
HomeWifiViewModel(
@@ -183,6 +193,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar on keyguard. */
@@ -193,6 +204,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar in quick settings. */
@@ -203,6 +215,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index dc73d1f007c6..f63d65246d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -36,6 +36,7 @@ import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -190,7 +191,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
- mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground);
+ mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
+ Expandable.fromView(mUserAvatarViewWithBackground));
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 1a25e4df3a61..1a8aafb1a5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -18,7 +18,6 @@ package com.android.systemui.temporarydisplay.chipbar
import android.content.Context
import android.graphics.Rect
-import android.media.MediaRoute2Info
import android.os.PowerManager
import android.view.Gravity
import android.view.MotionEvent
@@ -27,25 +26,25 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
-import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.media.taptotransfer.sender.ChipStateSender
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
-import com.android.systemui.media.taptotransfer.sender.TransferStatus
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -78,11 +77,11 @@ open class ChipbarCoordinator @Inject constructor(
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
-) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
+ private val vibratorHelper: VibratorHelper,
+) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -104,15 +103,13 @@ open class ChipbarCoordinator @Inject constructor(
override fun start() {}
override fun updateView(
- newInfo: ChipSenderInfo,
+ newInfo: ChipbarInfo,
currentView: ViewGroup
) {
// TODO(b/245610654): Adding logging here.
- val chipState = newInfo.state
-
// Detect falsing touches on the chip.
- parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
+ parent = currentView.requireViewById(R.id.chipbar_root_view)
parent.touchHandler = object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
falsingCollector.onTouchEvent(ev)
@@ -120,47 +117,54 @@ open class ChipbarCoordinator @Inject constructor(
}
}
- // App icon
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
- context, newInfo.routeInfo.clientPackageName, logger
- )
- val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
- iconView.setImageDrawable(iconInfo.drawable)
- iconView.contentDescription = iconInfo.contentDescription
+ // ---- Start icon ----
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
+ IconViewBinder.bind(newInfo.startIcon, iconView)
- // Text
- val otherDeviceName = newInfo.routeInfo.name.toString()
- val chipText = chipState.getChipTextString(context, otherDeviceName)
- currentView.requireViewById<TextView>(R.id.text).text = chipText
+ // ---- Text ----
+ val textView = currentView.requireViewById<TextView>(R.id.text)
+ TextViewBinder.bind(textView, newInfo.text)
+ // ---- End item ----
// Loading
currentView.requireViewById<View>(R.id.loading).visibility =
- (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue()
-
- // Undo
- val undoView = currentView.requireViewById<View>(R.id.undo)
- val undoClickListener = chipState.undoClickListener(
- this,
- newInfo.routeInfo,
- newInfo.undoCallback,
- uiEventLogger,
- falsingManager,
- )
- undoView.setOnClickListener(undoClickListener)
- undoView.visibility = (undoClickListener != null).visibleIfTrue()
+ (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue()
+
+ // Error
+ currentView.requireViewById<View>(R.id.error).visibility =
+ (newInfo.endItem == ChipbarEndItem.Error).visibleIfTrue()
+
+ // Button
+ val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
+ if (newInfo.endItem is ChipbarEndItem.Button) {
+ TextViewBinder.bind(buttonView, newInfo.endItem.text)
+
+ val onClickListener = View.OnClickListener { clickedView ->
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+ newInfo.endItem.onClickListener.onClick(clickedView)
+ }
- // Failure
- currentView.requireViewById<View>(R.id.failure_icon).visibility =
- (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue()
+ buttonView.setOnClickListener(onClickListener)
+ buttonView.visibility = View.VISIBLE
+ } else {
+ buttonView.visibility = View.GONE
+ }
- // For accessibility
+ // ---- Overall accessibility ----
currentView.requireViewById<ViewGroup>(
- R.id.media_ttt_sender_chip_inner
- ).contentDescription = "${iconInfo.contentDescription} $chipText"
+ R.id.chipbar_inner
+ ).contentDescription =
+ "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " +
+ "${newInfo.text.loadText(context)}"
+
+ // ---- Haptics ----
+ newInfo.vibrationEffect?.let {
+ vibratorHelper.vibrate(it)
+ }
}
override fun animateViewIn(view: ViewGroup) {
- val chipInnerView = view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner)
+ val chipInnerView = view.requireViewById<ViewGroup>(R.id.chipbar_inner)
ViewHierarchyAnimator.animateAddition(
chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
@@ -175,7 +179,7 @@ open class ChipbarCoordinator @Inject constructor(
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
ViewHierarchyAnimator.animateRemoval(
- view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ view.requireViewById<ViewGroup>(R.id.chipbar_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
ANIMATION_DURATION,
@@ -197,13 +201,5 @@ open class ChipbarCoordinator @Inject constructor(
}
}
-data class ChipSenderInfo(
- val state: ChipStateSender,
- val routeInfo: MediaRoute2Info,
- val undoCallback: IUndoMediaTransferCallback? = null
-) : TemporaryViewInfo {
- override fun getTimeoutMs() = state.timeout
-}
-
const val SENDER_TAG = "MediaTapToTransferSender"
private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
new file mode 100644
index 000000000000..57fde87114d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.os.VibrationEffect
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
+
+/**
+ * A container for all the state needed to display a chipbar via [ChipbarCoordinator].
+ *
+ * @property startIcon the icon to display at the start of the chipbar (on the left in LTR locales;
+ * on the right in RTL locales).
+ * @property text the text to display.
+ * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
+ * locales; on the left in RTL locales).
+ * @property vibrationEffect an optional vibration effect when the chipbar is displayed
+ */
+data class ChipbarInfo(
+ val startIcon: Icon,
+ val text: Text,
+ val endItem: ChipbarEndItem?,
+ val vibrationEffect: VibrationEffect? = null,
+) : TemporaryViewInfo
+
+/** The possible items to display at the end of the chipbar. */
+sealed class ChipbarEndItem {
+ /** A loading icon should be displayed. */
+ object Loading : ChipbarEndItem()
+
+ /** An error icon should be displayed. */
+ object Error : ChipbarEndItem()
+
+ /**
+ * A button with the provided [text] and [onClickListener] functionality should be displayed.
+ */
+ data class Button(val text: Text, val onClickListener: View.OnClickListener) : ChipbarEndItem()
+
+ // TODO(b/245610654): Add support for a generic icon.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3d56f2317660..3ecb15b9d79c 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -79,6 +79,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -114,6 +115,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final boolean mIsMonochromaticEnabled;
private final Context mContext;
private final boolean mIsMonetEnabled;
private final UserTracker mUserTracker;
@@ -363,6 +365,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
@Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
+ mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
@@ -665,8 +668,13 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
// Allow-list of Style objects that can be created from a setting string, i.e. can be
// used as a system-wide theme.
// - Content intentionally excluded, intended for media player, not system-wide
- List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
- Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
+ List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
+ Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT));
+
+ if (mIsMonochromaticEnabled) {
+ validStyles.add(Style.MONOCHROMATIC);
+ }
+
Style style = mThemeStyle;
final String overlayPackageJson = mSecureSettings.getStringForUser(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index ee785b62bd50..088cd93bdf7e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -36,9 +36,7 @@ class UserSwitcherPopupMenu(
private var adapter: ListAdapter? = null
init {
- setBackgroundDrawable(
- res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
- )
+ setBackgroundDrawable(null)
setModal(false)
setOverlapAnchor(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index d768b6dc195a..b16dc5403a57 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -220,7 +220,12 @@ constructor(
val result = withContext(backgroundDispatcher) { manager.aliveUsers }
if (result != null) {
- _userInfos.value = result.sortedBy { it.creationTime }
+ _userInfos.value =
+ result
+ // Users should be sorted by ascending creation time.
+ .sortedBy { it.creationTime }
+ // The guest user is always last, regardless of creation time.
+ .sortedBy { it.isGuest }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 0d5c64b83e6e..dda78aad54c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -429,6 +429,7 @@ constructor(
isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
)
return
@@ -443,6 +444,7 @@ constructor(
isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
)
return
@@ -477,6 +479,7 @@ constructor(
userHandle = currentUser.userHandle,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ dialogShower = dialogShower,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 08d7c5a26a25..177356e6b573 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,14 +18,18 @@
package com.android.systemui.user.domain.model
import android.os.UserHandle
+import com.android.systemui.qs.user.UserSwitchDialogController
/** Encapsulates a request to show a dialog. */
-sealed class ShowDialogRequestModel {
+sealed class ShowDialogRequestModel(
+ open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+) {
data class ShowAddUserDialog(
val userHandle: UserHandle,
val isKeyguardShowing: Boolean,
val showEphemeralMessage: Boolean,
- ) : ShowDialogRequestModel()
+ override val dialogShower: UserSwitchDialogController.DialogShower?,
+ ) : ShowDialogRequestModel(dialogShower)
data class ShowUserCreationDialog(
val isGuest: Boolean,
@@ -37,5 +41,6 @@ sealed class ShowDialogRequestModel {
val isGuestEphemeral: Boolean,
val isKeyguardShowing: Boolean,
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
- ) : ShowDialogRequestModel()
+ override val dialogShower: UserSwitchDialogController.DialogShower?,
+ ) : ShowDialogRequestModel(dialogShower)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 938417f9dbe3..968af59e6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -18,12 +18,15 @@
package com.android.systemui.user.ui.binder
import android.content.Context
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.SHOW_DIVIDER_MIDDLE
import android.widget.TextView
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
@@ -36,6 +39,7 @@ import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
+import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
@@ -168,15 +172,10 @@ object UserSwitcherViewBinder {
onDismissed: () -> Unit,
): UserSwitcherPopupMenu {
return UserSwitcherPopupMenu(context).apply {
+ this.setDropDownGravity(Gravity.END)
this.anchorView = anchorView
setAdapter(adapter)
setOnDismissListener { onDismissed() }
- setOnItemClickListener { _, _, position, _ ->
- val itemPositionExcludingHeader = position - 1
- adapter.getItem(itemPositionExcludingHeader).onClicked()
- dismiss()
- }
-
show()
}
}
@@ -186,38 +185,67 @@ object UserSwitcherViewBinder {
private val layoutInflater: LayoutInflater,
) : BaseAdapter() {
- private val items = mutableListOf<UserActionViewModel>()
+ private var sections = listOf<List<UserActionViewModel>>()
override fun getCount(): Int {
- return items.size
+ return sections.size
}
- override fun getItem(position: Int): UserActionViewModel {
- return items[position]
+ override fun getItem(position: Int): List<UserActionViewModel> {
+ return sections[position]
}
override fun getItemId(position: Int): Long {
- return getItem(position).viewKey
+ return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val view =
- convertView
- ?: layoutInflater.inflate(
+ val section = getItem(position)
+ val context = parent.context
+ val sectionView =
+ convertView as? LinearLayout
+ ?: LinearLayout(context, null).apply {
+ this.orientation = LinearLayout.VERTICAL
+ this.background =
+ parent.resources.getDrawable(
+ R.drawable.bouncer_user_switcher_popup_bg,
+ context.theme
+ )
+ this.showDividers = SHOW_DIVIDER_MIDDLE
+ this.dividerDrawable =
+ context.getDrawable(
+ R.drawable.fullscreen_userswitcher_menu_item_divider
+ )
+ }
+ sectionView.removeAllViewsInLayout()
+
+ for (viewModel in section) {
+ val view =
+ layoutInflater.inflate(
R.layout.user_switcher_fullscreen_popup_item,
- parent,
- false
+ /* parent= */ null
)
- val viewModel = getItem(position)
- view.requireViewById<ImageView>(R.id.icon).setImageResource(viewModel.iconResourceId)
- view.requireViewById<TextView>(R.id.text).text =
- view.resources.getString(viewModel.textResourceId)
- return view
+ view
+ .requireViewById<ImageView>(R.id.icon)
+ .setImageResource(viewModel.iconResourceId)
+ view.requireViewById<TextView>(R.id.text).text =
+ view.resources.getString(viewModel.textResourceId)
+ view.setOnClickListener { viewModel.onClicked() }
+ sectionView.addView(view)
+ }
+ return sectionView
}
fun setItems(items: List<UserActionViewModel>) {
- this.items.clear()
- this.items.addAll(items)
+ val primarySection =
+ items.filter {
+ it.viewKey != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+ }
+ val secondarySection =
+ items.filter {
+ it.viewKey == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+ }
+ this.sections = listOf(primarySection, secondarySection)
notifyDataSetChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index f7e19c0ca810..e9217209530b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -19,8 +19,10 @@ package com.android.systemui.user.ui.dialog
import android.app.Dialog
import android.content.Context
+import com.android.internal.jank.InteractionJankMonitor
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
@@ -71,37 +73,58 @@ constructor(
}
}
- currentDialog =
+ val (dialog, dialogCuj) =
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
- AddUserDialog(
- context = context.get(),
- userHandle = request.userHandle,
- isKeyguardShowing = request.isKeyguardShowing,
- showEphemeralMessage = request.showEphemeralMessage,
- falsingManager = falsingManager.get(),
- broadcastSender = broadcastSender.get(),
- dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ Pair(
+ AddUserDialog(
+ context = context.get(),
+ userHandle = request.userHandle,
+ isKeyguardShowing = request.isKeyguardShowing,
+ showEphemeralMessage = request.showEphemeralMessage,
+ falsingManager = falsingManager.get(),
+ broadcastSender = broadcastSender.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_ADD_NEW_USER_TAG,
+ ),
)
is ShowDialogRequestModel.ShowUserCreationDialog ->
- UserCreatingDialog(
- context.get(),
- request.isGuest,
+ Pair(
+ UserCreatingDialog(
+ context.get(),
+ request.isGuest,
+ ),
+ null,
)
is ShowDialogRequestModel.ShowExitGuestDialog ->
- ExitGuestDialog(
- context = context.get(),
- guestUserId = request.guestUserId,
- isGuestEphemeral = request.isGuestEphemeral,
- targetUserId = request.targetUserId,
- isKeyguardShowing = request.isKeyguardShowing,
- falsingManager = falsingManager.get(),
- dialogLaunchAnimator = dialogLaunchAnimator.get(),
- onExitGuestUserListener = request.onExitGuestUser,
+ Pair(
+ ExitGuestDialog(
+ context = context.get(),
+ guestUserId = request.guestUserId,
+ isGuestEphemeral = request.isGuestEphemeral,
+ targetUserId = request.targetUserId,
+ isKeyguardShowing = request.isKeyguardShowing,
+ falsingManager = falsingManager.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ onExitGuestUserListener = request.onExitGuestUser,
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
)
}
+ currentDialog = dialog
+
+ if (request.dialogShower != null && dialogCuj != null) {
+ request.dialogShower?.showDialog(dialog, dialogCuj)
+ } else {
+ dialog.show()
+ }
- currentDialog?.show()
interactor.get().onDialogShown()
}
}
@@ -120,4 +143,9 @@ constructor(
}
}
}
+
+ companion object {
+ private const val INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"
+ private const val INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index ecb365f43e3f..2c317dd391c0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -172,10 +172,14 @@ public abstract class Condition implements CallbackController<Condition.Callback
return Boolean.TRUE.equals(mIsConditionMet);
}
- private boolean shouldLog() {
+ protected final boolean shouldLog() {
return Log.isLoggable(mTag, Log.DEBUG);
}
+ protected final String getTag() {
+ return mTag;
+ }
+
/**
* Callback that receives updates about whether the condition has been fulfilled.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4824f6744c6e..cb430ba454f0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -117,6 +117,7 @@ public class Monitor {
final SubscriptionState state = new SubscriptionState(subscription);
mExecutor.execute(() -> {
+ if (shouldLog()) Log.d(mTag, "adding subscription");
mSubscriptions.put(token, state);
// Add and associate conditions.
@@ -143,7 +144,7 @@ public class Monitor {
*/
public void removeSubscription(@NotNull Subscription.Token token) {
mExecutor.execute(() -> {
- if (shouldLog()) Log.d(mTag, "removing callback");
+ if (shouldLog()) Log.d(mTag, "removing subscription");
if (!mSubscriptions.containsKey(token)) {
Log.e(mTag, "subscription not present:" + token);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
new file mode 100644
index 000000000000..b7166d96d401
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.util;
+
+option java_multiple_files = true;
+
+message ComponentNameProto {
+ string package_name = 1;
+ string class_name = 2;
+}
diff --git a/packages/SystemUI/tests/res/layout/custom_view_dark.xml b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
index 9e460a5819a9..112d73d2d7f2 100644
--- a/packages/SystemUI/tests/res/layout/custom_view_dark.xml
+++ b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/custom_view_dark_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
index 9d6aff219148..7b9b39f23c29 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -66,4 +66,13 @@ class BouncerKeyguardMessageAreaTest : SysuiTestCase() {
underTest.setMessage(null)
assertThat(underTest.text).isEqualTo("")
}
+
+ @Test
+ fun testSetNullClearsPreviousMessage() {
+ underTest.setMessage("something not null")
+ assertThat(underTest.text).isEqualTo("something not null")
+
+ underTest.setMessage(null)
+ assertThat(underTest.text).isEqualTo("")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 8a2c35410586..03efd06d0e5e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -17,17 +17,21 @@ package com.android.keyguard
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
+import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
@@ -37,6 +41,9 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import java.util.TimeZone
import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -57,7 +64,7 @@ import org.mockito.junit.MockitoJUnit
class ClockEventControllerTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -72,8 +79,11 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var largeClockController: ClockFaceController
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
+ @Mock private lateinit var parentView: View
+ @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
+ private lateinit var repository: FakeKeyguardRepository
- private lateinit var clockEventController: ClockEventController
+ private lateinit var underTest: ClockEventController
@Before
fun setUp() {
@@ -86,8 +96,11 @@ class ClockEventControllerTest : SysuiTestCase() {
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
- clockEventController = ClockEventController(
- statusBarStateController,
+ repository = FakeKeyguardRepository()
+
+ underTest = ClockEventController(
+ KeyguardInteractor(repository = repository),
+ KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -98,31 +111,33 @@ class ClockEventControllerTest : SysuiTestCase() {
bgExecutor,
featureFlags
)
+ underTest.clock = clock
+
+ runBlocking(IMMEDIATE) {
+ underTest.registerListeners(parentView)
+
+ repository.setDozing(true)
+ repository.setDozeAmount(1f)
+ }
}
@Test
fun clockSet_validateInitialization() {
- clockEventController.clock = clock
-
verify(clock).initialize(any(), anyFloat(), anyFloat())
}
@Test
fun clockUnset_validateState() {
- clockEventController.clock = clock
- clockEventController.clock = null
+ underTest.clock = null
- assertEquals(clockEventController.clock, null)
+ assertEquals(underTest.clock, null)
}
@Test
- fun themeChanged_verifyClockPaletteUpdated() {
- clockEventController.clock = clock
+ fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
- clockEventController.registerListeners()
-
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onThemeChanged()
@@ -131,13 +146,10 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun fontChanged_verifyFontSizeUpdated() {
- clockEventController.clock = clock
+ fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
- clockEventController.registerListeners()
-
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
@@ -146,10 +158,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -161,26 +170,21 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations, times(1)).charge()
- }
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, times(1)).charge()
+ }
@Test
- fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -192,25 +196,20 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, false)
-
- verify(animations, never()).charge()
- }
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
+
+ verify(animations, never()).charge()
+ }
@Test
- fun localeCallback_verifyClockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<BroadcastReceiver>()
verify(broadcastDispatcher).registerReceiver(
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
@@ -221,10 +220,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_visibilityChanged_clockDozeCalled() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
@@ -236,10 +232,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_timeFormat_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeFormatChanged("12h")
@@ -248,11 +241,8 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_timezoneChanged_clockNotified() {
+ fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
val mockTimeZone = mock<TimeZone>()
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeZoneChanged(mockTimeZone)
@@ -261,10 +251,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_userSwitched_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onUserSwitchComplete(10)
@@ -273,25 +260,27 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_verifyKeyguardChanged() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
+ fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
+ val job = underTest.listenForDozeAmount(this)
+ repository.setDozeAmount(0.4f)
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(capture(captor))
- captor.value.onDozeAmountChanged(0.4f, 0.6f)
+ yield()
verify(animations).doze(0.4f)
+
+ job.cancel()
}
@Test
- fun unregisterListeners_validate() {
- clockEventController.clock = clock
- clockEventController.unregisterListeners()
+ fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
+ underTest.unregisterListeners()
verify(broadcastDispatcher).unregisterReceiver(any())
verify(configurationController).removeCallback(any())
verify(batteryController).removeCallback(any())
verify(keyguardUpdateMonitor).removeCallback(any())
- verify(statusBarStateController).removeCallback(any())
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9b2bba612106..627d738a895f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -280,6 +280,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
- verify(mClockEventController, times).registerListeners();
+ verify(mClockEventController, times).registerListeners(mView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 48e82397e826..b885d546c517 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -146,6 +146,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+ @Captor
+ private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
private Configuration mConfiguration;
@@ -475,6 +477,64 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
}
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardUpdateMonitor, never())
+ .requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardPasswordViewControllerMock).showMessage(null, null);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
+ }
+
+ private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
+ return mSwipeListenerArgumentCaptor.getValue();
+ }
+
private void setupConditionsToEnableSideFpsHint() {
attachView();
setSideFpsHintEnabledFromResources(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7281bc8c851b..c6233b54c028 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -26,6 +26,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -648,6 +649,36 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
}
+ @Test
+ public void requestFaceAuth_whenFaceAuthWasStarted_returnsTrue() throws RemoteException {
+ // This satisfies all the preconditions to run face auth.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ NOTIFICATION_PANEL_CLICKED);
+
+ assertThat(didFaceAuthRun).isTrue();
+ }
+
+ @Test
+ public void requestFaceAuth_whenFaceAuthWasNotStarted_returnsFalse() throws RemoteException {
+ // This ensures face auth won't run.
+ biometricsDisabledForCurrentUser();
+ mTestableLooper.processAllMessages();
+
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ NOTIFICATION_PANEL_CLICKED);
+
+ assertThat(didFaceAuthRun).isFalse();
+ }
+
private void testStrongAuthExceptOnBouncer(int strongAuth) {
when(mKeyguardBypassController.canBypass()).thenReturn(true);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
new file mode 100644
index 000000000000..6391a2c8eff7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.PointF;
+import android.testing.AndroidTestingRunner;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MenuAnimationController}. */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class MenuAnimationControllerTest extends SysuiTestCase {
+ private MenuView mMenuView;
+ private MenuAnimationController mMenuAnimationController;
+
+ @Before
+ public void setUp() throws Exception {
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ stubWindowManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+ mMenuAnimationController = new MenuAnimationController(mMenuView);
+ }
+
+ @Test
+ public void moveToPosition_matchPosition() {
+ final PointF destination = new PointF(50, 60);
+
+ mMenuAnimationController.moveToPosition(destination);
+
+ assertThat(mMenuView.getTranslationX()).isEqualTo(50);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(60);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index d8b10e04705e..e62a3295a7e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
import android.testing.AndroidTestingRunner;
@@ -25,6 +26,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,13 +44,24 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {
@Mock
private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
+ private MenuInfoRepository mMenuInfoRepository;
+
+ @Before
+ public void setUp() {
+ mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged);
+ }
+
@Test
public void menuSizeTypeChanged_verifyOnSizeTypeChanged() {
- final MenuInfoRepository menuInfoRepository =
- new MenuInfoRepository(mContext, mMockSettingsContentsChanged);
-
- menuInfoRepository.mMenuSizeContentObserver.onChange(true);
+ mMenuInfoRepository.mMenuSizeContentObserver.onChange(true);
verify(mMockSettingsContentsChanged).onSizeTypeChanged(anyInt());
}
+
+ @Test
+ public void menuOpacityChanged_verifyOnFadeEffectChanged() {
+ mMenuInfoRepository.mMenuFadeOutContentObserver.onChange(true);
+
+ verify(mMockSettingsContentsChanged).onFadeEffectInfoChanged(any(MenuFadeEffectInfo.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
new file mode 100644
index 000000000000..bf6d574a0f67
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link MenuItemAccessibilityDelegate}. */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ private RecyclerView mStubListView;
+ private MenuView mMenuView;
+ private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
+ private MenuAnimationController mMenuAnimationController;
+ private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
+
+ @Before
+ public void setUp() {
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ stubWindowManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+
+ final int halfScreenHeight =
+ stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView.setTranslationY(halfScreenHeight);
+
+ doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
+ mStubListView = new RecyclerView(mContext);
+ mMenuAnimationController = spy(new MenuAnimationController(mMenuView));
+ mMenuItemAccessibilityDelegate =
+ new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
+ mStubListView), mMenuAnimationController);
+ }
+
+ @Test
+ public void getAccessibilityActionList_matchSize() {
+ final AccessibilityNodeInfoCompat info =
+ new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+ mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+ assertThat(info.getActionList().size()).isEqualTo(5);
+ }
+
+ @Test
+ public void performMoveTopLeftAction_matchPosition() {
+ final boolean moveTopLeftAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_top_left,
+ null);
+
+ assertThat(moveTopLeftAction).isTrue();
+ assertThat(mMenuView.getTranslationX()).isEqualTo(mDraggableBounds.left);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(mDraggableBounds.top);
+ }
+
+ @Test
+ public void performMoveTopRightAction_matchPosition() {
+ final boolean moveTopRightAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_top_right, null);
+
+ assertThat(moveTopRightAction).isTrue();
+ assertThat(mMenuView.getTranslationX()).isEqualTo(mDraggableBounds.right);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(mDraggableBounds.top);
+ }
+
+ @Test
+ public void performMoveBottomLeftAction_matchPosition() {
+ final boolean moveBottomLeftAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_bottom_left, null);
+
+ assertThat(moveBottomLeftAction).isTrue();
+ assertThat(mMenuView.getTranslationX()).isEqualTo(mDraggableBounds.left);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(mDraggableBounds.bottom);
+ }
+
+ @Test
+ public void performMoveBottomRightAction_matchPosition() {
+ final boolean moveBottomRightAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_bottom_right, null);
+
+ assertThat(moveBottomRightAction).isTrue();
+ assertThat(mMenuView.getTranslationX()).isEqualTo(mDraggableBounds.right);
+ assertThat(mMenuView.getTranslationY()).isEqualTo(mDraggableBounds.bottom);
+ }
+
+ @Test
+ public void performMoveToEdgeAndHideAction_success() {
+ final boolean moveToEdgeAndHideAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_to_edge_and_hide, null);
+
+ assertThat(moveToEdgeAndHideAction).isTrue();
+ verify(mMenuAnimationController).moveToEdgeAndHide();
+ }
+
+ @Test
+ public void performMoveOutFromEdgeAction_success() {
+ final boolean moveOutEdgeAndShowAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_move_out_edge_and_show, null);
+
+ assertThat(moveOutEdgeAndShowAction).isTrue();
+ verify(mMenuAnimationController).moveOutEdgeAndShow();
+ }
+
+ @Test
+ public void performFocusAction_fadeIn() {
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ ACTION_ACCESSIBILITY_FOCUS, null);
+
+ verify(mMenuAnimationController).fadeInNowIfEnabled();
+ }
+
+ @Test
+ public void performClearFocusAction_fadeOut() {
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+
+ verify(mMenuAnimationController).fadeOutIfEnabled();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
new file mode 100644
index 000000000000..c5b9a294fc34
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.view.View.OVER_SCROLL_NEVER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.MotionEventHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Tests for {@link MenuListViewTouchHandler}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class MenuListViewTouchHandlerTest extends SysuiTestCase {
+ private final List<AccessibilityTarget> mStubTargets = new ArrayList<>(
+ Collections.singletonList(mock(AccessibilityTarget.class)));
+ private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+ private MenuView mStubMenuView;
+ private MenuListViewTouchHandler mTouchHandler;
+ private MenuAnimationController mMenuAnimationController;
+ private RecyclerView mStubListView;
+
+ @Before
+ public void setUp() throws Exception {
+ final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ windowManager);
+ mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+ mStubMenuView.setTranslationX(0);
+ mStubMenuView.setTranslationY(0);
+ mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
+ mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController);
+ final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
+ mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
+ mStubListView.setAdapter(stubAdapter);
+ }
+
+ @Test
+ public void onActionDownEvent_shouldCancelAnimations() {
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+
+ verify(mMenuAnimationController).cancelAnimations();
+ }
+
+ @Test
+ public void onActionMoveEvent_shouldMoveToPosition() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+ mStubListView.setOverScrollMode(OVER_SCROLL_NEVER);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ assertThat(mStubMenuView.getTranslationX()).isEqualTo(offset);
+ assertThat(mStubMenuView.getTranslationY()).isEqualTo(offset);
+ }
+
+ @Test
+ public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+ final MotionEvent stubUpEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 5,
+ MotionEvent.ACTION_UP, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent);
+
+ verify(mMenuAnimationController).flingMenuThenSpringToEdge(anyFloat(), anyFloat(),
+ anyFloat());
+ }
+
+ @Test
+ public void dragMenuOutOfBoundsAndDrop_moveToLeftEdge_shouldMoveToEdgeAndHide() {
+ final int offset = -100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+ final MotionEvent stubUpEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 5,
+ MotionEvent.ACTION_UP, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent);
+
+ verify(mMenuAnimationController).moveToEdgeAndHide();
+ }
+
+ @After
+ public void tearDown() {
+ mMotionEventHelper.recycleEvents();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index f782a446c627..8c8d6aca7cd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -16,13 +16,24 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
@@ -38,6 +49,7 @@ import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuViewLayerController}. */
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuViewLayerControllerTest extends SysuiTestCase {
@Rule
@@ -46,10 +58,20 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
private MenuViewLayerController mMenuViewLayerController;
@Before
public void setUp() throws Exception {
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
+ mWindowManager).getMaximumWindowMetrics();
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
+ when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager);
}
@@ -68,4 +90,14 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
verify(mWindowManager).removeView(any(View.class));
}
+
+ private WindowInsets stubDisplayInsets() {
+ final int stubStatusBarHeight = 118;
+ final int stubNavigationBarHeight = 125;
+ return new WindowInsets.Builder()
+ .setVisible(systemBars() | displayCutout(), true)
+ .setInsets(systemBars() | displayCutout(),
+ Insets.of(0, stubStatusBarHeight, 0, stubNavigationBarHeight))
+ .build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 8883cb783438..23c6ef1338b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -44,7 +45,8 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
- mMenuViewLayer = new MenuViewLayer(mContext);
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 513044d2c20d..742ee53e99b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -24,11 +24,15 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.UiModeManager;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
@@ -45,6 +49,8 @@ public class MenuViewTest extends SysuiTestCase {
private int mNightMode;
private UiModeManager mUiModeManager;
private MenuView mMenuView;
+ private String mLastPosition;
+ private MenuViewAppearance mStubMenuViewAppearance;
@Before
public void setUp() throws Exception {
@@ -52,8 +58,11 @@ public class MenuViewTest extends SysuiTestCase {
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
- final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
+ mLastPosition = Prefs.getString(mContext,
+ Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
}
@Test
@@ -74,8 +83,58 @@ public class MenuViewTest extends SysuiTestCase {
assertThat(areInsetsMatched).isTrue();
}
+ @Test
+ public void onDraggingStart_matchInsets() {
+ mMenuView.onDraggingStart();
+ final InstantInsetLayerDrawable insetLayerDrawable =
+ (InstantInsetLayerDrawable) mMenuView.getBackground();
+
+ assertThat(insetLayerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM)).isEqualTo(0);
+ assertThat(insetLayerDrawable.getLayerInsetTop(INDEX_MENU_ITEM)).isEqualTo(0);
+ assertThat(insetLayerDrawable.getLayerInsetRight(INDEX_MENU_ITEM)).isEqualTo(0);
+ assertThat(insetLayerDrawable.getLayerInsetBottom(INDEX_MENU_ITEM)).isEqualTo(0);
+ }
+
+ @Test
+ public void onAnimationend_updatePositionForSharedPreference() {
+ final float percentageX = 0.0f;
+ final float percentageY = 0.5f;
+
+ mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
+ final String positionString = Prefs.getString(mContext,
+ Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
+ final Position position = Position.fromString(positionString);
+
+ assertThat(position.getPercentageX()).isEqualTo(percentageX);
+ assertThat(position.getPercentageY()).isEqualTo(percentageY);
+ }
+
+ @Test
+ public void onEdgeChangedIfNeeded_moveToLeftEdge_matchRadii() {
+ final Rect draggableBounds = mStubMenuViewAppearance.getMenuDraggableBounds();
+ mMenuView.setTranslationX(draggableBounds.right);
+
+ mMenuView.setTranslationX(draggableBounds.left);
+ mMenuView.onEdgeChangedIfNeeded();
+ final float[] radii = getMenuViewGradient().getCornerRadii();
+
+ assertThat(radii[0]).isEqualTo(0.0f);
+ assertThat(radii[1]).isEqualTo(0.0f);
+ assertThat(radii[6]).isEqualTo(0.0f);
+ assertThat(radii[7]).isEqualTo(0.0f);
+ }
+
+ private InstantInsetLayerDrawable getMenuViewInsetLayer() {
+ return (InstantInsetLayerDrawable) mMenuView.getBackground();
+ }
+
+ private GradientDrawable getMenuViewGradient() {
+ return (GradientDrawable) getMenuViewInsetLayer().getDrawable(INDEX_MENU_ITEM);
+ }
+
@After
public void tearDown() throws Exception {
mUiModeManager.setNightMode(mNightMode);
+ Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index cd50144bf2e8..d489656559c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -124,14 +125,18 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
whenever(udfpsEnrollView.context).thenReturn(context)
}
- private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
+ private fun withReason(
+ @ShowReason reason: Int,
+ isDebuggable: Boolean = false,
+ block: () -> Unit
+ ) {
controllerOverlay = UdfpsControllerOverlay(
context, fingerprintManager, inflater, windowManager, accessibilityManager,
statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator
+ controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
)
block()
}
@@ -151,11 +156,29 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
}
@Test
+ fun showUdfpsOverlay_locate_withEnrollmentUiRemoved() {
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+ withReason(REASON_ENROLL_FIND_SENSOR, isDebuggable = true) {
+ showUdfpsOverlay(isEnrollUseCase = false)
+ }
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+ }
+
+ @Test
fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) {
showUdfpsOverlay(isEnrollUseCase = true)
}
@Test
+ fun showUdfpsOverlay_enroll_withEnrollmentUiRemoved() {
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+ withReason(REASON_ENROLL_ENROLLING, isDebuggable = true) {
+ showUdfpsOverlay(isEnrollUseCase = false)
+ }
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+ }
+
+ @Test
fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
@@ -373,21 +396,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_0
// touch at 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
// touch at 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
// touch at 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
}
fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -395,21 +430,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_90
// touch at 0 degrees -> 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 90 degrees -> 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
// touch at 180 degrees -> 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
// touch at 270 degrees -> 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
}
fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -417,21 +464,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_270
// touch at 0 degrees -> 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
// touch at 90 degrees -> 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
// touch at 180 degrees -> 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 270 degrees -> 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index eff47bd2ee98..49c6fd14997e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -665,7 +665,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it is cancelled
- mUdfpsController.onCancelUdfps();
+ mUdfpsController.cancelAodInterrupt();
// THEN the display is unconfigured
verify(mUdfpsView).unconfigureDisplay();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3e9cf1e51b63..fa9c41a3cbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -71,6 +72,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
+ @Mock
private BatteryController mBatteryController;
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -85,7 +88,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
- mStatusBarStateController, mKeyguardStateController, mBatteryController,
+ mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
+ mBatteryController,
mDockManager, mFakeExecutor, mFakeSystemClock);
}
@@ -137,9 +141,9 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
public void testUnregisterSensor_QS() {
mFalsingCollector.onScreenTurningOn();
reset(mProximitySensor);
- mFalsingCollector.setQsExpanded(true);
+ mFalsingCollector.onQsExpansionChanged(true);
verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
- mFalsingCollector.setQsExpanded(false);
+ mFalsingCollector.onQsExpansionChanged(false);
verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index d96ca91e36bd..677c7bdcffe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -40,6 +40,7 @@ import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.screenshot.TimeoutHandler;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -47,6 +48,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@Ignore("b/254635291")
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ClipboardOverlayControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index b33f9a7f3933..2f206adc5acf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -425,7 +425,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testGesturesAllInitiallyRespectSettings() {
- DozeSensors dozeSensors = new DozeSensors(getContext(), mSensorManager, mDozeParameters,
+ DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController);
@@ -437,7 +437,7 @@ public class DozeSensorsTest extends SysuiTestCase {
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
- super(getContext(), mSensorManager, mDozeParameters,
+ super(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 781dc1550048..6091d3a93f14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -23,10 +23,10 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -88,6 +88,8 @@ public class DozeTriggersTest extends SysuiTestCase {
@Mock
private ProximityCheck mProximityCheck;
@Mock
+ private DozeLog mDozeLog;
+ @Mock
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -127,7 +129,7 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
- mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
+ mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
mDevicePostureController);
mTriggers.setDozeMachine(mMachine);
@@ -342,6 +344,16 @@ public class DozeTriggersTest extends SysuiTestCase {
verify(mProximityCheck).destroy();
}
+ @Test
+ public void testIsExecutingTransition_dropPulse() {
+ when(mHost.isPulsePending()).thenReturn(false);
+ when(mMachine.isExecutingTransition()).thenReturn(true);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
+
+ verify(mDozeLog).tracePulseDropped(anyString(), eq(null));
+ }
+
private void waitForSensorManager() {
mExecutor.runAllReady();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 65b44a14d2ad..65ae90b8f7e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -19,11 +19,17 @@ package com.android.systemui.dump
import androidx.test.filters.SmallTest
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -31,9 +37,6 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
-import javax.inject.Provider
@SmallTest
class DumpHandlerTest : SysuiTestCase() {
@@ -47,6 +50,8 @@ class DumpHandlerTest : SysuiTestCase() {
@Mock
private lateinit var pw: PrintWriter
+ @Mock
+ private lateinit var fd: FileDescriptor
@Mock
private lateinit var dumpable1: Dumpable
@@ -56,6 +61,11 @@ class DumpHandlerTest : SysuiTestCase() {
private lateinit var dumpable3: Dumpable
@Mock
+ private lateinit var protoDumpable1: ProtoDumpable
+ @Mock
+ private lateinit var protoDumpable2: ProtoDumpable
+
+ @Mock
private lateinit var buffer1: LogBuffer
@Mock
private lateinit var buffer2: LogBuffer
@@ -88,7 +98,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN some of them are dumped explicitly
val args = arrayOf("dumpable1", "dumpable3", "buffer2")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN only the requested ones have their dump() method called
verify(dumpable1).dump(pw, args)
@@ -107,7 +117,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN that module is dumped
val args = arrayOf("dumpable1")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN its dump() method is called
verify(dumpable1).dump(pw, args)
@@ -124,7 +134,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN a critical dump is requested
val args = arrayOf("--dump-priority", "CRITICAL")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all modules are dumped (but no buffers)
verify(dumpable1).dump(pw, args)
@@ -145,7 +155,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN a normal dump is requested
val args = arrayOf("--dump-priority", "NORMAL")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all buffers are dumped (but no modules)
verify(dumpable1, never()).dump(
@@ -168,11 +178,35 @@ class DumpHandlerTest : SysuiTestCase() {
val spw = PrintWriter(stringWriter)
// When a config dump is requested
- dumpHandler.dump(spw, arrayOf("config"))
+ dumpHandler.dump(fd, spw, arrayOf("config"))
assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
}
+ @Test
+ fun testDumpAllProtoDumpables() {
+ dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+ val args = arrayOf(DumpHandler.PROTO)
+ dumpHandler.dump(fd, pw, args)
+
+ verify(protoDumpable1).dumpProto(any(), eq(args))
+ verify(protoDumpable2).dumpProto(any(), eq(args))
+ }
+
+ @Test
+ fun testDumpSingleProtoDumpable() {
+ dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+ val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
+ dumpHandler.dump(fd, pw, args)
+
+ verify(protoDumpable1).dumpProto(any(), eq(args))
+ verify(protoDumpable2, never()).dumpProto(any(), any())
+ }
+
private class EmptyCoreStartable : CoreStartable {
override fun start() {}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 4c6113870737..9628ee93ceff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -51,14 +51,6 @@ class FlagCommandTest : SysuiTestCase() {
}
@Test
- fun noOpCommand() {
- cmd.execute(pw, ArrayList())
- Mockito.verify(pw, Mockito.atLeastOnce()).println()
- Mockito.verify(featureFlags).isEnabled(flagA)
- Mockito.verify(featureFlags).isEnabled(flagB)
- }
-
- @Test
fun readFlagCommand() {
cmd.execute(pw, listOf(flagA.id.toString()))
Mockito.verify(featureFlags).isEnabled(flagA)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
new file mode 100644
index 000000000000..1b34100b1cef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider
+import android.animation.ValueAnimator
+import android.util.Log
+import android.util.Log.TerribleFailure
+import android.util.Log.TerribleFailureHandler
+import android.view.Choreographer.FrameCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+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.google.common.truth.Truth.assertThat
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardTransitionRepository
+ private lateinit var oldWtfHandler: TerribleFailureHandler
+ private lateinit var wtfHandler: WtfHandler
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardTransitionRepository()
+ wtfHandler = WtfHandler()
+ oldWtfHandler = Log.setWtfHandler(wtfHandler)
+ }
+
+ @After
+ fun tearDown() {
+ oldWtfHandler?.let { Log.setWtfHandler(it) }
+ }
+
+ @Test
+ fun `startTransition runs animator to completion`() =
+ runBlocking(IMMEDIATE) {
+ val (animator, provider) = setupAnimator(this)
+
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+
+ val startTime = System.currentTimeMillis()
+ while (animator.isRunning()) {
+ yield()
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ assertSteps(steps, listWithStep(BigDecimal(.1)))
+
+ job.cancel()
+ provider.stop()
+ }
+
+ @Test
+ fun `startTransition called during another transition fails`() {
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
+ underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Null animator enables manual control with updateTransition`() =
+ runBlocking(IMMEDIATE) {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ }
+
+ assertThat(steps.size).isEqualTo(3)
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ assertThat(steps[2])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ job.cancel()
+ }
+
+ @Test
+ fun `Attempt to manually update transition with invalid UUID throws exception`() {
+ underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Attempt to manually update transition after FINISHED state throws exception`() {
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ }
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+ val steps = mutableListOf<BigDecimal>()
+
+ var i = BigDecimal.ZERO
+ while (i.compareTo(BigDecimal.ONE) <= 0) {
+ steps.add(i)
+ i = (i + step).setScale(2, RoundingMode.HALF_UP)
+ }
+
+ return steps
+ }
+
+ private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+ // + 2 accounts for start and finish of automated transition
+ assertThat(steps.size).isEqualTo(fractions.size + 2)
+
+ assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ fractions.forEachIndexed { index, fraction ->
+ assertThat(steps[index + 1])
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ )
+ }
+ assertThat(steps[steps.size - 1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+
+ assertThat(wtfHandler.failed).isFalse()
+ }
+
+ private fun setupAnimator(
+ scope: CoroutineScope
+ ): Pair<ValueAnimator, TestFrameCallbackProvider> {
+ val animator =
+ ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(ANIMATION_DURATION)
+ }
+
+ val provider = TestFrameCallbackProvider(animator, scope)
+ provider.start()
+
+ return Pair(animator, provider)
+ }
+
+ /** Gives direct control over ValueAnimator. See [AnimationHandler] */
+ private class TestFrameCallbackProvider(
+ private val animator: ValueAnimator,
+ private val scope: CoroutineScope,
+ ) : AnimationFrameCallbackProvider {
+
+ private var frameCount = 1L
+ private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
+ private var job: Job? = null
+
+ fun start() {
+ animator.getAnimationHandler().setProvider(this)
+
+ job =
+ scope.launch {
+ frames.collect {
+ // Delay is required for AnimationHandler to properly register a callback
+ delay(1)
+ val (frameNumber, callback) = it
+ callback?.doFrame(frameNumber)
+ }
+ }
+ }
+
+ fun stop() {
+ job?.cancel()
+ animator.getAnimationHandler().setProvider(null)
+ }
+
+ override fun postFrameCallback(cb: FrameCallback) {
+ frames.value = Pair(++frameCount, cb)
+ }
+ override fun postCommitCallback(runnable: Runnable) {}
+ override fun getFrameTime() = frameCount
+ override fun getFrameDelay() = 1L
+ override fun setFrameDelay(delay: Long) {}
+ }
+
+ private class WtfHandler : TerribleFailureHandler {
+ var failed = false
+ override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
+ failed = true
+ }
+ }
+
+ companion object {
+ private const val MAX_TEST_DURATION = 100L
+ private const val ANIMATION_DURATION = 10L
+ private const val OWNER_NAME = "Test"
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 7c83cb74bb77..6a4c0f60466d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -22,6 +22,9 @@ import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -62,6 +65,34 @@ class MediaTttUtilsTest : SysuiTestCase() {
}
@Test
+ fun getIconFromPackageName_nullPackageName_returnsDefault() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
+
+ val expectedDesc =
+ ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+ .loadContentDescription(context)
+ assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+ }
+
+ @Test
+ fun getIconFromPackageName_invalidPackageName_returnsDefault() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
+
+ val expectedDesc =
+ ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+ .loadContentDescription(context)
+ assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+ }
+
+ @Test
+ fun getIconFromPackageName_validPackageName_returnsAppInfo() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
+
+ assertThat(icon)
+ .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
+ }
+
+ @Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
val iconInfo =
MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 110bbb80df3a..fdeb3f5eb857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -17,14 +17,19 @@
package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import android.media.MediaRoute2Info
import android.os.PowerManager
+import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
@@ -32,16 +37,18 @@ import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.chipbar.ChipSenderInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -60,20 +67,29 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class MediaTttSenderCoordinatorTest : SysuiTestCase() {
+
+ // Note: This tests are a bit like integration tests because they use a real instance of
+ // [ChipbarCoordinator] and verify that the coordinator displays the correct view, based on
+ // the inputs from [MediaTttSenderCoordinator].
+
private lateinit var underTest: MediaTttSenderCoordinator
@Mock private lateinit var accessibilityManager: AccessibilityManager
+ @Mock private lateinit var applicationInfo: ApplicationInfo
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var logger: MediaTttLogger
@Mock private lateinit var mediaTttFlags: MediaTttFlags
+ @Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private lateinit var chipbarCoordinator: ChipbarCoordinator
private lateinit var commandQueueCallback: CommandQueue.Callbacks
+ private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -85,6 +101,18 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+ fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+ whenever(
+ packageManager.getApplicationInfo(
+ eq(PACKAGE_NAME),
+ any<PackageManager.ApplicationInfoFlags>()
+ )
+ )
+ .thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
@@ -100,10 +128,10 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
accessibilityManager,
configurationController,
powerManager,
- uiEventLogger,
falsingManager,
falsingCollector,
viewUtil,
+ vibratorHelper,
)
chipbarCoordinator.start()
@@ -149,10 +177,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME))
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -163,10 +198,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME))
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -177,12 +219,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -193,12 +240,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -209,12 +261,66 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
+ verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ var undoCallbackCalled = false
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ },
+ )
+
+ getChipbarView().getUndoButton().performClick()
+
+ // Event index 1 since initially displaying the succeeded chip would also log an event
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
+ assertThat(undoCallbackCalled).isTrue()
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
}
@Test
@@ -225,12 +331,68 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
+ verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ var undoCallbackCalled = false
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ },
+ )
+
+ getChipbarView().getUndoButton().performClick()
+
+ // Event index 1 since initially displaying the succeeded chip would also log an event
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
+ )
+ assertThat(undoCallbackCalled).isTrue()
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
}
@Test
@@ -241,12 +403,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -257,12 +424,17 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
null
)
- assertThat(getChipView().getChipText())
- .isEqualTo(
- transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@Test
@@ -407,53 +579,113 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
verify(windowManager).removeView(any())
}
- private fun getChipView(): ViewGroup {
+ @Test
+ fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+
+ // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+ // verify that the new state it triggers operates just like any other state.
+ getChipbarView().getUndoButton().performClick()
+ fakeExecutor.runAllReady()
+
+ // Verify that the click updated us to the triggered state
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ // Verify that we didn't remove the chipbar because it's in the triggered state
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ // Verify we eventually remove the chipbar
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+
+ // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+ // verify that the new state it triggers operates just like any other state.
+ getChipbarView().getUndoButton().performClick()
+ fakeExecutor.runAllReady()
+
+ // Verify that the click updated us to the triggered state
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ // Verify that we didn't remove the chipbar because it's in the triggered state
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ // Verify we eventually remove the chipbar
+ verify(windowManager).removeView(any())
+ }
+
+ private fun getChipbarView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any())
return viewCaptor.value as ViewGroup
}
+ private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.start_icon)
+
private fun ViewGroup.getChipText(): String =
(this.requireViewById<TextView>(R.id.text)).text as String
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToStartCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToEndCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
+ private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
+ private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
+ private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+ private fun ChipStateSender.getExpectedStateText(): String? {
+ return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
+ }
}
+private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val PACKAGE_NAME = "com.android.systemui"
private const val TIMEOUT = 10000
private val routeInfo =
MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
- .setClientPackageName("com.android.systemui")
+ .setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 0badd861787d..1bc4719c70b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -147,6 +147,18 @@ public class ColorSchemeTest extends SysuiTestCase {
}
@Test
+ public void testMonochromatic() {
+ int colorInt = 0xffB3588A; // H350 C50 T50
+ ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
+ Style.MONOCHROMATIC /* style */);
+ int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ Assert.assertTrue(
+ Color.red(neutralMid) == Color.green(neutralMid)
+ && Color.green(neutralMid) == Color.blue(neutralMid)
+ );
+ }
+
+ @Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void testToString() {
new ColorScheme(Color.TRANSPARENT, false /* darkTheme */).toString();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 1c686c66e31e..5e9c1aaad309 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -22,7 +22,6 @@ import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -52,6 +51,7 @@ import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -97,6 +97,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
private ViewGroup mRootView;
+ private ViewGroup mSecurityFooterView;
private TextView mFooterText;
private TestableImageView mPrimaryFooterIcon;
private QSSecurityFooter mFooter;
@@ -121,21 +122,26 @@ public class QSSecurityFooterTest extends SysuiTestCase {
Looper looper = mTestableLooper.getLooper();
Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
- mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
+ mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build().inflate(R.layout.quick_settings_security_footer, null, false);
mFooterUtils = new QSSecurityFooterUtils(getContext(),
getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
- mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
- mBroadcastDispatcher, mFooterUtils);
- mFooterText = mRootView.findViewById(R.id.footer_text);
- mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
+ mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
+ looper, mBroadcastDispatcher, mFooterUtils);
+ mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
+ mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
.thenReturn(DEVICE_OWNER_COMPONENT);
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+ // mSecurityFooterView must have a ViewGroup parent so that
+ // DialogLaunchAnimator.Controller.fromView() does not return null.
+ mRootView = new FrameLayout(mContext);
+ mRootView.addView(mSecurityFooterView);
ViewUtils.attachView(mRootView);
mFooter.init();
@@ -153,7 +159,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mRootView.getVisibility());
+ assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -165,7 +171,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -181,7 +187,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION),
mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -200,7 +206,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(
R.string.quick_settings_financed_disclosure_named_management,
MANAGING_ORGANIZATION), mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -217,7 +223,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mRootView.getVisibility());
+ assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -227,8 +233,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertFalse(mRootView.isClickable());
- assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertFalse(mSecurityFooterView.isClickable());
+ assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -241,8 +247,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertTrue(mRootView.isClickable());
- assertEquals(View.VISIBLE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertTrue(mSecurityFooterView.isClickable());
+ assertEquals(View.VISIBLE,
+ mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -254,8 +261,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertFalse(mRootView.isClickable());
- assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertFalse(mSecurityFooterView.isClickable());
+ assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -734,11 +741,11 @@ public class QSSecurityFooterTest extends SysuiTestCase {
@Test
public void testDialogUsesDialogLauncher() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
- mFooter.onClick(mRootView);
+ mFooter.onClick(mSecurityFooterView);
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView), any());
+ verify(mDialogLaunchAnimator).show(any(), any());
}
@Test
@@ -775,7 +782,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any(), any());
+ verify(mDialogLaunchAnimator).show(dialogCaptor.capture(), any());
AlertDialog dialog = dialogCaptor.getValue();
dialog.create();
@@ -817,8 +824,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
any());
- // Pretend view is not visible temporarily
- mRootView.onVisibilityAggregated(false);
+ // Pretend view is not attached anymore.
+ mRootView.removeView(mSecurityFooterView);
captor.getValue().onReceive(mContext,
new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 3c58b6fc1354..c452872a527e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -52,6 +52,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -114,8 +115,6 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private DumpManager mDumpManager;
@Mock
- private QSTile.State mMockState;
- @Mock
private CentralSurfaces mCentralSurfaces;
@Mock
private QSLogger mQSLogger;
@@ -195,7 +194,6 @@ public class QSTileHostTest extends SysuiTestCase {
}
private void setUpTileFactory() {
- when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
when(mDefaultFactory.createTile(anyString())).thenAnswer(
invocation -> {
@@ -209,7 +207,11 @@ public class QSTileHostTest extends SysuiTestCase {
} else if ("na".equals(spec)) {
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
- return mCustomTile;
+ QSTile tile = mCustomTile;
+ QSTile.State s = mock(QSTile.State.class);
+ s.spec = spec;
+ when(mCustomTile.getState()).thenReturn(s);
+ return tile;
} else if ("internet".equals(spec)
|| "wifi".equals(spec)
|| "cell".equals(spec)) {
@@ -647,7 +649,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testSetTileRemoved_removedBySystem() {
int user = mUserTracker.getUserId();
- saveSetting("spec1" + CUSTOM_TILE_SPEC);
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
// This will be done by TileServiceManager
mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
@@ -658,6 +660,27 @@ public class QSTileHostTest extends SysuiTestCase {
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
+ @Test
+ public void testProtoDump_noTiles() {
+ SystemUIProtoDump proto = new SystemUIProtoDump();
+ mQSTileHost.dumpProto(proto, new String[0]);
+
+ assertEquals(0, proto.tiles.length);
+ }
+
+ @Test
+ public void testTilesInOrder() {
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+ SystemUIProtoDump proto = new SystemUIProtoDump();
+ mQSTileHost.dumpProto(proto, new String[0]);
+
+ assertEquals(2, proto.tiles.length);
+ assertEquals("spec1", proto.tiles[0].getSpec());
+ assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
+ assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
+ }
+
private SharedPreferences getSharedPreferenecesForUser(int user) {
return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
}
@@ -707,12 +730,9 @@ public class QSTileHostTest extends SysuiTestCase {
@Override
public State newTileState() {
- return mMockState;
- }
-
- @Override
- public State getState() {
- return mMockState;
+ State s = mock(QSTile.State.class);
+ when(s.toString()).thenReturn(MOCK_STATE_STRING);
+ return s;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
new file mode 100644
index 000000000000..629c663943db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.qs
+
+import android.content.ComponentName
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TileStateToProtoTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "label"
+ private const val TEST_SUBTITLE = "subtitle"
+ private const val TEST_SPEC = "spec"
+ private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+ }
+
+ @Test
+ fun platformTile_INACTIVE() {
+ val state =
+ QSTile.State().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_INACTIVE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(proto?.hasBooleanState()).isFalse()
+ }
+
+ @Test
+ fun componentTile_UNAVAILABLE() {
+ val state =
+ QSTile.State().apply {
+ spec = CustomTile.toSpec(TEST_COMPONENT)
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_UNAVAILABLE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isFalse()
+ assertThat(proto?.hasComponentName()).isTrue()
+ val componentName = proto?.componentName
+ assertThat(componentName?.packageName).isEqualTo(TEST_COMPONENT.packageName)
+ assertThat(componentName?.className).isEqualTo(TEST_COMPONENT.className)
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ assertThat(proto?.hasBooleanState()).isFalse()
+ }
+
+ @Test
+ fun booleanState_ACTIVE() {
+ val state =
+ QSTile.BooleanState().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_ACTIVE
+ value = true
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(proto?.hasBooleanState()).isTrue()
+ assertThat(proto?.booleanState).isTrue()
+ }
+
+ @Test
+ fun noSpec_returnsNull() {
+ val state =
+ QSTile.State().apply {
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_ACTIVE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 3c258077c29d..2c2ddbb9b8c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -23,13 +23,13 @@ import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
@@ -70,13 +70,13 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(qsSecurityFooterUtils = qsSecurityFooterUtils)
val quickSettingsContext = mock<Context>()
- underTest.showDeviceMonitoringDialog(quickSettingsContext)
- verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
- val view = mock<View>()
- whenever(view.context).thenReturn(quickSettingsContext)
- underTest.showDeviceMonitoringDialog(view)
+ underTest.showDeviceMonitoringDialog(quickSettingsContext, null)
verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
+
+ val expandable = mock<Expandable>()
+ underTest.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+ verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
@Test
@@ -85,8 +85,8 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(uiEventLogger = uiEventLogger)
val globalActionsDialogLite = mock<GlobalActionsDialogLite>()
- val view = mock<View>()
- underTest.showPowerMenuDialog(globalActionsDialogLite, view)
+ val expandable = mock<Expandable>()
+ underTest.showPowerMenuDialog(globalActionsDialogLite, expandable)
// Event is logged.
val logs = uiEventLogger.logs
@@ -99,7 +99,7 @@ class FooterActionsInteractorTest : SysuiTestCase() {
.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
- view,
+ expandable,
)
}
@@ -167,11 +167,11 @@ class FooterActionsInteractorTest : SysuiTestCase() {
userSwitchDialogController = userSwitchDialogController,
)
- val view = mock<View>()
- underTest.showUserSwitcher(view)
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
- verify(userSwitchDialogController).showDialog(view)
+ verify(userSwitchDialogController).showDialog(context, expandable)
}
@Test
@@ -184,12 +184,9 @@ class FooterActionsInteractorTest : SysuiTestCase() {
activityStarter = activityStarter,
)
- // The clicked view. The context is necessary because it's used to build the intent, that
- // we check below.
- val view = mock<View>()
- whenever(view.context).thenReturn(context)
-
- underTest.showUserSwitcher(view)
+ // The clicked expandable.
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
val intentCaptor = argumentCaptor<Intent>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 9d908fdfb976..0a34810f4d3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -20,12 +20,12 @@ import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
import android.testing.AndroidTestingRunner
-import android.view.View
import android.widget.Button
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,7 +64,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Mock
private lateinit var userDetailViewAdapter: UserDetailView.Adapter
@Mock
- private lateinit var launchView: View
+ private lateinit var launchExpandable: Expandable
@Mock
private lateinit var neutralButton: Button
@Mock
@@ -79,7 +80,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(launchView.context).thenReturn(mContext)
`when`(dialog.context).thenReturn(mContext)
controller = UserSwitchDialogController(
@@ -94,32 +94,34 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Test
fun showDialog_callsDialogShow() {
- controller.showDialog(launchView)
- verify(dialogLaunchAnimator).showFromView(eq(dialog), eq(launchView), any(), anyBoolean())
+ val launchController = mock<DialogLaunchAnimator.Controller>()
+ `when`(launchExpandable.dialogLaunchController(any())).thenReturn(launchController)
+ controller.showDialog(context, launchExpandable)
+ verify(dialogLaunchAnimator).show(eq(dialog), eq(launchController), anyBoolean())
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@Test
fun dialog_showForAllUsers() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setShowForAllUsers(true)
}
@Test
fun dialog_cancelOnTouchOutside() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setCanceledOnTouchOutside(true)
}
@Test
fun adapterAndGridLinked() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
}
@Test
fun doneButtonLogsCorrectly() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
@@ -132,7 +134,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_noFalsing_opensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -153,7 +155,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_Falsing_notOpensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d095add1c660..e444a3909408 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -108,6 +109,7 @@ import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
@@ -254,6 +256,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private PrivacyDotViewController mPrivacyDotViewController;
@Mock private NavigationModeController mNavigationModeController;
+ @Mock private NavigationBarController mNavigationBarController;
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@Mock private ContentResolver mContentResolver;
@Mock private TapAgainViewController mTapAgainViewController;
@@ -377,6 +380,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
+ mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
mInteractionJankMonitor),
@@ -392,6 +396,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mConfigurationController,
mStatusBarStateController,
mFalsingManager,
+ mShadeExpansionStateManager,
mLockscreenShadeTransitionController,
new FalsingCollectorFake(),
mDumpManager);
@@ -430,6 +435,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
+ when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
@@ -473,6 +479,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mPrivacyDotViewController,
mTapAgainViewController,
mNavigationModeController,
+ mNavigationBarController,
mFragmentService,
mContentResolver,
mRecordingController,
@@ -757,6 +764,38 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ // Start shade collapse with swipe up
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+
+ // simulate touch that does not exceed touch slop
+ onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+ 2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ mNotificationPanelViewController.setTouchSlopExceeded(false);
+
+ onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+ 2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ // fling should still be called after a touch that does not exceed touch slop
+ assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ }
+
+ @Test
public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(false);
@@ -1568,7 +1607,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
+ when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1583,7 +1622,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+ when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index 12ef036d89d0..bdafc7df33bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -66,6 +66,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Mock
private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
@Mock
+ private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock
private lateinit var featureFlags: FeatureFlags
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@@ -96,6 +98,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
navigationModeController,
overviewProxyService,
largeScreenShadeHeaderController,
+ shadeExpansionStateManager,
featureFlags,
delayableExecutor
)
@@ -380,6 +383,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
navigationModeController,
overviewProxyService,
largeScreenShadeHeaderController,
+ shadeExpansionStateManager,
featureFlags,
delayableExecutor
)
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 ad3d3d2958cb..95cf9d60b511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -88,6 +88,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private AuthController mAuthController;
+ @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -103,7 +104,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController) {
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager) {
@Override
protected boolean isDebuggable() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 44cbe51a30ac..fbb8ebfb3e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
@@ -56,6 +57,7 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
private val configurationController: ConfigurationController = mock()
private val statusBarStateController: StatusBarStateController = mock()
private val falsingManager: FalsingManager = mock()
+ private val shadeExpansionStateManager: ShadeExpansionStateManager = mock()
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
private val falsingCollector: FalsingCollector = mock()
private val dumpManager: DumpManager = mock()
@@ -65,7 +67,8 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
fun setUp() {
whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
- pulseExpansionHandler = PulseExpansionHandler(
+ pulseExpansionHandler =
+ PulseExpansionHandler(
mContext,
wakeUpCoordinator,
bypassController,
@@ -74,10 +77,11 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
configurationController,
statusBarStateController,
falsingManager,
+ shadeExpansionStateManager,
lockscreenShadeTransitionController,
falsingCollector,
dumpManager
- )
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 4b458f5a9123..dda7fadde2d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -31,8 +31,8 @@ public class GroupEntryBuilder {
private long mCreationTime = 0;
@Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
private NotifSection mNotifSection;
- private NotificationEntry mSummary = null;
- private List<NotificationEntry> mChildren = new ArrayList<>();
+ @Nullable private NotificationEntry mSummary = null;
+ private final List<NotificationEntry> mChildren = new ArrayList<>();
/** Builds a new instance of GroupEntry */
public GroupEntry build() {
@@ -41,7 +41,9 @@ public class GroupEntryBuilder {
ge.getAttachState().setSection(mNotifSection);
ge.setSummary(mSummary);
- mSummary.setParent(ge);
+ if (mSummary != null) {
+ mSummary.setParent(ge);
+ }
for (NotificationEntry child : mChildren) {
ge.addChild(child);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 851517e1e35b..3b05321e1a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1498,45 +1498,8 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
- public void testMissingRankingWhenRemovalFeatureIsDisabled() {
+ public void testMissingRanking() {
// GIVEN a pipeline with one two notifications
- when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false);
- String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
- String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
- NotificationEntry entry1 = mCollectionListener.getEntry(key1);
- NotificationEntry entry2 = mCollectionListener.getEntry(key2);
- clearInvocations(mCollectionListener);
-
- // GIVEN the message for removing key1 gets does not reach NotifCollection
- Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
- // WHEN the message for removing key2 arrives
- mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
-
- // THEN only entry2 gets removed
- verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
- verify(mCollectionListener).onEntryCleanUp(eq(entry2));
- verify(mCollectionListener).onRankingApplied();
- verifyNoMoreInteractions(mCollectionListener);
- verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
- verify(mLogger, never()).logRecoveredRankings(any(), anyInt());
- clearInvocations(mCollectionListener, mLogger);
-
- // WHEN a ranking update includes key1 again
- mNoMan.setRanking(key1, ranking1);
- mNoMan.issueRankingUpdate();
-
- // VERIFY that we do nothing but log the 'recovery'
- verify(mCollectionListener).onRankingUpdate(any());
- verify(mCollectionListener).onRankingApplied();
- verifyNoMoreInteractions(mCollectionListener);
- verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
- verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0));
- }
-
- @Test
- public void testMissingRankingWhenRemovalFeatureIsEnabled() {
- // GIVEN a pipeline with one two notifications
- when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true);
String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
NotificationEntry entry1 = mCollectionListener.getEntry(key1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 340bc96f80c2..3ff7639e9262 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -674,7 +674,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
+ // and is new enough to do so
assertFalse(mEntry.hasInterrupted())
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
setShouldHeadsUp(mEntry)
whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
@@ -690,8 +692,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
- // GIVEN that mEntry has alerted in the past
+ // GIVEN that mEntry has alerted in the past, even if it's new
mEntry.setInterruption()
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
setShouldHeadsUp(mEntry)
whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
@@ -725,6 +728,27 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
verify(mHeadsUpManager).showNotification(mEntry)
}
+ @Test
+ fun testOnRankingApplied_entryUpdatedButTooOld() {
+ // GIVEN that mEntry is added in a state where it should not HUN
+ setShouldHeadsUp(mEntry, false)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // and it was actually added 10s ago
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+ // WHEN it is updated to HUN and then a ranking update occurs
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification is never bound or shown
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+ }
+
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f4adf6927e31..b6b0b7738997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -181,7 +181,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testInflatesNewNotification() {
// WHEN there is a new notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
// THEN we inflate it
@@ -194,7 +194,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testRebindsInflatedNotificationsOnUpdate() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -213,7 +213,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntrySmartReplyAdditionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -232,7 +232,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntryChangedToMinimizedSectionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
@@ -254,7 +254,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
// GIVEN an inflated, minimized notification
setSectionIsLowPriority(true);
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertTrue(mParamsCaptor.getValue().isLowPriority());
@@ -275,7 +275,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntryRankChangeWillNotRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -294,7 +294,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testDoesntFilterInflatedNotifs() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -330,9 +330,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mCollectionListener.onEntryInit(entry);
}
- mCollectionListener.onEntryAdded(summary);
+ mCollectionListener.onEntryInit(summary);
for (NotificationEntry entry : children) {
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
}
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry));
@@ -393,6 +393,70 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testNullGroupSummary() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(getNotificationEntryBuilder().setId(1).build())
+ .addChild(getNotificationEntryBuilder().setId(2).build())
+ .addChild(getNotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN the summary is pruned
+ new GroupEntryBuilder()
+ .setCreationTime(400)
+ .addChild(child0)
+ .addChild(child1)
+ .build();
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.invokeInflateCallbackForEntry(child0);
+ mNotifInflater.invokeInflateCallbackForEntry(child1);
+
+ // THEN the entire group is not filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
+ public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() {
+ // GIVEN a newly-posted group with a summary and two children
+ final String groupKey = "test_reinflate_group";
+ final int summaryId = 1;
+ final GroupEntry group = new GroupEntryBuilder()
+ .setKey(groupKey)
+ .setCreationTime(400)
+ .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build())
+ .addChild(getNotificationEntryBuilder().setId(2).build())
+ .addChild(getNotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry summary = group.getSummary();
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.invokeInflateCallbackForEntry(child0);
+ mNotifInflater.invokeInflateCallbackForEntry(child1);
+ mNotifInflater.invokeInflateCallbackForEntry(summary);
+
+ // WHEN the summary is updated and starts re-inflating
+ summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build());
+ fireUpdateEvents(summary);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // THEN the entire group is still not filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
public void testCompletedInflatedGroupsAreReleased() {
// GIVEN a newly-posted group with a summary and two children
final GroupEntry group = new GroupEntryBuilder()
@@ -412,7 +476,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mNotifInflater.invokeInflateCallbackForEntry(child1);
mNotifInflater.invokeInflateCallbackForEntry(summary);
- // THEN the entire group is still filtered out
+ // THEN the entire group is no longer filtered out
assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
@@ -494,7 +558,11 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private void fireAddEvents(NotificationEntry entry) {
mCollectionListener.onEntryInit(entry);
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
+ }
+
+ private void fireUpdateEvents(NotificationEntry entry) {
+ mCollectionListener.onEntryUpdated(entry);
}
private static final String TEST_MESSAGE = "TEST_MESSAGE";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 46f630b7db63..ea311da3e20b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -51,12 +51,14 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -97,6 +99,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
NotifPipelineFlags mFlags;
@Mock
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ UiEventLoggerFake mUiEventLoggerFake;
@Mock
PendingIntent mPendingIntent;
@@ -107,6 +110,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ mUiEventLoggerFake = new UiEventLoggerFake();
+
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
mContext.getContentResolver(),
@@ -120,7 +125,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
mLogger,
mMockHandler,
mFlags,
- mKeyguardNotificationVisibilityProvider);
+ mKeyguardNotificationVisibilityProvider,
+ mUiEventLoggerFake);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
@@ -442,6 +448,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
@Test
@@ -600,6 +613,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index 16e2441c556b..f69839b7087c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -28,30 +28,21 @@ import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class NotificationMemoryMonitorTest : SysuiTestCase() {
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
+class NotificationMemoryMeterTest : SysuiTestCase() {
@Test
fun currentNotificationMemoryUse_plainNotification() {
val notification = createBasicNotification().build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -69,8 +60,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -92,8 +83,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
RemoteViews(context.packageName, android.R.layout.list_content)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -112,8 +103,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
val notification =
createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = 444444,
@@ -141,8 +132,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
.bigLargeIcon(bigPictureIcon)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -167,8 +158,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
createBasicNotification()
.setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -203,8 +194,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
.addHistoricMessage(historicMessage)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -225,8 +216,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
val extender = Notification.CarExtender().setLargeIcon(carIcon)
val notification = createBasicNotification().extend(extender).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -246,8 +237,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -283,10 +274,10 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
extender: Int,
style: String?,
styleIcon: Int,
- hasCustomView: Boolean
+ hasCustomView: Boolean,
) {
assertThat(memoryUse.packageName).isEqualTo("test_pkg")
- assertThat(memoryUse.notificationId)
+ assertThat(memoryUse.notificationKey)
.isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
@@ -301,21 +292,14 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
}
private fun getUseObject(
- singleItemUseList: List<NotificationMemoryUsage>
+ singleItemUseList: List<NotificationMemoryUsage>,
): NotificationMemoryUsage {
assertThat(singleItemUseList).hasSize(1)
return singleItemUseList[0]
}
- private fun createNMMWithNotifications(
- notifications: List<Notification>
- ): NotificationMemoryMonitor {
- val notifPipeline: NotifPipeline = mock()
- val notificationEntries =
- notifications.map { n ->
- NotificationEntryBuilder().setTag("test").setNotification(n).build()
- }
- whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
- return NotificationMemoryMonitor(notifPipeline, mock())
- }
+ private fun createNotificationEntry(
+ notification: Notification,
+ ): NotificationEntry =
+ NotificationEntryBuilder().setTag("test").setNotification(notification).build()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
new file mode 100644
index 000000000000..3a16fb33388b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -0,0 +1,148 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.tests.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationMemoryViewWalkerTest : SysuiTestCase() {
+
+ private lateinit var testHelper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ }
+
+ @Test
+ fun testViewWalker_nullRow_returnsEmptyView() {
+ val result = NotificationMemoryViewWalker.getViewUsage(null)
+ assertThat(result).isNotNull()
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun testViewWalker_plainNotification() {
+ val row = testHelper.createRow()
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_bigPictureNotification() {
+ val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setLargeIcon(largeIcon)
+ .setStyle(Notification.BigPictureStyle().bigPicture(bigPicture))
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount +
+ icon.bitmap.allocationByteCount +
+ largeIcon.bitmap.allocationByteCount
+ )
+ )
+
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
+ )
+ )
+ // Due to deduplication, this should all be 0.
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_customView() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+
+ val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
+ views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setCustomContentView(views)
+ .setCustomBigContentView(views)
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
+ // Due to deduplication, this should all be 0.
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ad497a2ec1e1..6de8bd5f3670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -80,6 +80,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -98,7 +99,8 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -271,7 +273,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private OngoingCallController mOngoingCallController;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
- @Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private WallpaperManager mWallpaperManager;
@Mock private IWallpaperManager mIWallpaperManager;
@@ -296,9 +297,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
- private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
- private InitController mInitController = new InitController();
+ private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final InitController mInitController = new InitController();
private final DumpManager mDumpManager = new DumpManager();
@Before
@@ -322,7 +324,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mock(NotificationInterruptLogger.class),
new Handler(TestableLooper.get(this).getLooper()),
mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class));
+ mock(KeyguardNotificationVisibilityProvider.class),
+ mock(UiEventLogger.class));
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1017,6 +1020,60 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
}
@Test
+ public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+ // GIVEN the shade is expanded
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+ // WHEN collapseShade is called
+ mCentralSurfaces.collapseShade();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+ // GIVEN the shade is collapsed
+ mCentralSurfaces.setPanelExpanded(false);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+ // WHEN collapseShade is called
+ mCentralSurfaces.collapseShade();
+
+ // VERIFY that animateCollapsePanels is NOT called
+ verify(mShadeController, never()).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+ // GIVEN the shade is expanded & flag enabled
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+ mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false);
+
+ // WHEN collapseShadeForBugreport is called
+ mCentralSurfaces.collapseShadeForBugreport();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+ // GIVEN the shade is expanded & flag enabled
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+ mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true);
+
+ // WHEN collapseShadeForBugreport is called
+ mCentralSurfaces.collapseShadeForBugreport();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController, never()).animateCollapsePanels();
+ }
+
+ @Test
public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
setFoldedStates(FOLD_STATE_FOLDED);
@@ -1102,7 +1159,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
super(
contentResolver,
powerManager,
@@ -1115,7 +1173,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
logger,
mainHandler,
flags,
- keyguardNotificationVisibilityProvider
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger
);
mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8da8d049516e..0c35659b458a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -117,7 +117,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
@Mock private BouncerInteractor mBouncerInteractor;
@Mock private BouncerView mBouncerView;
-// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
index bf432388ad28..eba3b04f3472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
@@ -20,7 +20,6 @@ import android.content.Intent
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -34,8 +33,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -91,7 +90,7 @@ class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
fun testStartActivity() {
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(View::class.java))
+ verify(userSwitcherDialogController).showDialog(any(), any())
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
statusBarUserSwitcherContainer.callOnClick()
verify(activityStarter).startActivity(any(Intent::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
new file mode 100644
index 000000000000..b7a6c0125cfa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.Looper
+import android.os.UserHandle
+import android.provider.Settings.Global
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class AirplaneModeRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeRepositoryImpl
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var bgHandler: Handler
+ private lateinit var scope: CoroutineScope
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ bgHandler = Handler(Looper.getMainLooper())
+ scope = CoroutineScope(IMMEDIATE)
+ settings = FakeSettings()
+ settings.userId = UserHandle.USER_ALL
+
+ underTest =
+ AirplaneModeRepositoryImpl(
+ bgHandler,
+ settings,
+ logger,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun isAirplaneMode_initiallyGetsSettingsValue() =
+ runBlocking(IMMEDIATE) {
+ settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+
+ underTest =
+ AirplaneModeRepositoryImpl(
+ bgHandler,
+ settings,
+ logger,
+ scope,
+ )
+
+ val job = underTest.isAirplaneMode.launchIn(this)
+
+ assertThat(underTest.isAirplaneMode.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneMode_settingUpdated_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isAirplaneMode.launchIn(this)
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isFalse()
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isTrue()
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
new file mode 100644
index 000000000000..63bbdfca0071
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.statusbar.pipeline.airplane.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeAirplaneModeRepository : AirplaneModeRepository {
+ private val _isAirplaneMode = MutableStateFlow(false)
+ override val isAirplaneMode: StateFlow<Boolean> = _isAirplaneMode
+
+ fun setIsAirplaneMode(isAirplaneMode: Boolean) {
+ _isAirplaneMode.value = isAirplaneMode
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
new file mode 100644
index 000000000000..33a80e1a3dd6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.statusbar.pipeline.airplane.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeInteractor
+
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+
+ @Before
+ fun setUp() {
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ connectivityRepository = FakeConnectivityRepository()
+ underTest = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+ }
+
+ @Test
+ fun isAirplaneMode_matchesRepo() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneMode.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ yield()
+ assertThat(latest).isFalse()
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoHasWifiHidden_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
new file mode 100644
index 000000000000..76016a121e68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.statusbar.pipeline.airplane.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeViewModelTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeViewModel
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+ private lateinit var interactor: AirplaneModeInteractor
+ private lateinit var scope: CoroutineScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ connectivityRepository = FakeConnectivityRepository()
+ interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+ scope = CoroutineScope(IMMEDIATE)
+
+ underTest =
+ AirplaneModeViewModel(
+ interactor,
+ logger,
+ scope,
+ )
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index f751afc195b2..2f18ce31217e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -27,6 +27,9 @@ class FakeWifiRepository : WifiRepository {
private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+ private val _isWifiDefault: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
MutableStateFlow(WifiNetworkModel.Inactive)
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
@@ -38,6 +41,10 @@ class FakeWifiRepository : WifiRepository {
_isWifiEnabled.value = enabled
}
+ fun setIsWifiDefault(default: Boolean) {
+ _isWifiDefault.value = default
+ }
+
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
_wifiNetwork.value = wifiNetworkModel
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 0ba0bd623c39..a64a4bd2e57a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -222,6 +222,83 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val wifiInfo = mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo)
+ )
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(mock())
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ // First, add a network
+ getDefaultNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ // WHEN the network is lost
+ getDefaultNetworkCallback().onLost(NETWORK)
+
+ // THEN we update to false
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
@@ -745,6 +822,12 @@ class WifiRepositoryImplTest : SysuiTestCase() {
return callbackCaptor.value!!
}
+ private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun createWifiNetworkCapabilities(
wifiInfo: WifiInfo,
isValidated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index 39b886af1cb8..71b8bab87d19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -178,6 +178,29 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
+ fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .isDefault
+ .onEach { latest = it }
+ .launchIn(this)
+
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ wifiRepository.setIsWifiDefault(false)
+ yield()
+ assertThat(latest).isFalse()
+
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
val wifiNetwork = WifiNetworkModel.Active(
networkId = 45,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 4efb13520ebf..c5841098010a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -30,6 +30,9 @@ import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -63,11 +66,13 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var viewModel: WifiViewModel
private lateinit var scope: CoroutineScope
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@@ -77,12 +82,22 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
+ airplaneModeViewModel = AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
viewModel = WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index a3ad028519bb..a1afcd71e3c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -22,11 +22,14 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -64,19 +67,31 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ airplaneModeViewModel =
+ AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
}
@After
@@ -88,6 +103,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
fun wifiIcon() =
runBlocking(IMMEDIATE) {
wifiRepository.setIsWifiEnabled(testCase.enabled)
+ wifiRepository.setIsWifiDefault(testCase.isDefault)
connectivityRepository.setForceHiddenIcons(
if (testCase.forceHidden) {
setOf(ConnectivitySlot.WIFI)
@@ -101,6 +117,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
.thenReturn(testCase.hasDataCapabilities)
underTest =
WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
@@ -125,19 +142,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
} else {
testCase.expected.contentDescription.invoke(context)
}
- assertThat(iconFlow.value?.contentDescription?.getAsString())
+ assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
.isEqualTo(expectedContentDescription)
job.cancel()
}
- private fun ContentDescription.getAsString(): String? {
- return when (this) {
- is ContentDescription.Loaded -> this.description
- is ContentDescription.Resource -> context.getString(this.res)
- }
- }
-
internal data class Expected(
/** The resource that should be used for the icon. */
@DrawableRes val iconResource: Int,
@@ -159,6 +169,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
val forceHidden: Boolean = false,
val alwaysShowIconWhenEnabled: Boolean = false,
val hasDataCapabilities: Boolean = true,
+ val isDefault: Boolean = false,
val network: WifiNetworkModel,
/** The expected output. Null if we expect the output to be null. */
@@ -169,6 +180,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
"forceHidden=$forceHidden, " +
"showWhenEnabled=$alwaysShowIconWhenEnabled, " +
"hasDataCaps=$hasDataCapabilities, " +
+ "isDefault=$isDefault, " +
"network=$network) then " +
"EXPECTED($expected)"
}
@@ -303,6 +315,46 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
),
),
+ // isDefault = true => all Inactive and Active networks shown
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Inactive,
+ expected =
+ Expected(
+ iconResource = WIFI_NO_NETWORK,
+ contentDescription = { context ->
+ "${context.getString(WIFI_NO_CONNECTION)}," +
+ context.getString(NO_INTERNET)
+ },
+ description = "No network icon",
+ ),
+ ),
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
+ expected =
+ Expected(
+ iconResource = WIFI_NO_INTERNET_ICONS[3],
+ contentDescription = { context ->
+ "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," +
+ context.getString(NO_INTERNET)
+ },
+ description = "No internet level 3 icon",
+ ),
+ ),
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1),
+ expected =
+ Expected(
+ iconResource = WIFI_FULL_ICONS[1],
+ contentDescription = { context ->
+ context.getString(WIFI_CONNECTION_STRENGTH[1])
+ },
+ description = "Full internet level 1 icon",
+ ),
+ ),
+
// network = CarrierMerged => not shown
TestCase(
network = WifiNetworkModel.CarrierMerged,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 3169eef83f07..7d2c56098584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,8 +20,12 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -55,19 +59,31 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ airplaneModeViewModel = AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
+
createAndSetViewModel()
}
@@ -76,6 +92,8 @@ class WifiViewModelTest : SysuiTestCase() {
scope.cancel()
}
+ // See [WifiViewModelIconParameterizedTest] for additional view model tests.
+
// Note on testing: [WifiViewModel] exposes 3 different instances of
// [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
// same data for icon, activity, etc. flows. So, most of these tests will test just one of the
@@ -460,11 +478,64 @@ class WifiViewModelTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
// allows tests to create the view model only after those configs are correctly set up.
underTest = WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 6225d0c722ae..9fbf159ec348 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -16,11 +16,8 @@
package com.android.systemui.temporarydisplay.chipbar
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.media.MediaRoute2Info
import android.os.PowerManager
+import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -31,19 +28,19 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.sender.ChipStateSender
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEvents
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -53,7 +50,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -64,437 +60,293 @@ import org.mockito.MockitoAnnotations
class ChipbarCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: FakeChipbarCoordinator
- @Mock
- private lateinit var packageManager: PackageManager
- @Mock
- private lateinit var applicationInfo: ApplicationInfo
- @Mock
- private lateinit var logger: MediaTttLogger
- @Mock
- private lateinit var accessibilityManager: AccessibilityManager
- @Mock
- private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var falsingManager: FalsingManager
- @Mock
- private lateinit var falsingCollector: FalsingCollector
- @Mock
- private lateinit var viewUtil: ViewUtil
- private lateinit var fakeAppIconDrawable: Drawable
+ @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var viewUtil: ViewUtil
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
private lateinit var uiEventLoggerFake: UiEventLoggerFake
- private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
- whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
- whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
- whenever(packageManager.getApplicationInfo(
- eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
- )).thenReturn(applicationInfo)
- context.setMockPackageManager(packageManager)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
uiEventLoggerFake = UiEventLoggerFake()
- senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
-
- whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
- underTest = FakeChipbarCoordinator(
- context,
- logger,
- windowManager,
- fakeExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- senderUiEventLogger,
- falsingManager,
- falsingCollector,
- viewUtil,
- )
+ underTest =
+ FakeChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ vibratorHelper,
+ )
underTest.start()
}
@Test
- fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToStartCast()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToEndCast()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
+ fun displayView_loadedIcon_correctlyRendered() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
- @Test
- fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToReceiverTriggered()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = null,
+ )
)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
- @Test
- fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToThisDeviceTriggered()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ val iconView = getChipbarView().getStartIconView()
+ assertThat(iconView.drawable).isEqualTo(drawable)
+ assertThat(iconView.contentDescription).isEqualTo("loadedCD")
}
@Test
- fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToReceiverSucceeded()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
+ fun displayView_resourceIcon_correctlyRendered() {
+ val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription),
+ Text.Loaded("text"),
+ endItem = null,
+ )
)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
- underTest.displayView(transferToReceiverSucceeded(undoCallback = null))
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ val iconView = getChipbarView().getStartIconView()
+ assertThat(iconView.contentDescription)
+ .isEqualTo(contentDescription.loadContentDescription(context))
}
@Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- underTest.displayView(transferToReceiverSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
+ fun displayView_loadedText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("display view text here"),
+ endItem = null,
+ )
+ )
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- underTest.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
+ assertThat(getChipbarView().getChipText()).isEqualTo("display view text here")
}
@Test
- fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
- whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- underTest.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
+ fun displayView_resourceText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Resource(R.string.screenrecord_start_error),
+ endItem = null,
+ )
+ )
- assertThat(undoCallbackCalled).isFalse()
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(context.getString(R.string.screenrecord_start_error))
}
@Test
- fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
- whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- underTest.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
+ fun displayView_endItemNull_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
- assertThat(undoCallbackCalled).isTrue()
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- underTest.displayView(transferToReceiverSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id
+ fun displayView_endItemLoading_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
)
- }
- @Test
- fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToThisDeviceSucceeded()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback = null))
+ fun displayView_endItemError_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
+ fun displayView_endItemButton_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ onClickListener = {},
+ ),
+ )
+ )
- @Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().text).isEqualTo("button text")
+ assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue()
}
@Test
- fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
+ fun displayView_endItemButtonClicked_falseTap_listenerNotRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
)
- }
- @Test
- fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToReceiverFailed()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
+ getChipbarView().getEndButton().performClick()
- @Test
- fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToThisDeviceFailed()
- underTest.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(isClicked).isFalse()
}
@Test
- fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
- underTest.displayView(almostCloseToStartCast())
- underTest.displayView(transferToReceiverTriggered())
+ fun displayView_endItemButtonClicked_notFalseTap_listenerRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- }
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
+ )
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
- underTest.displayView(transferToReceiverTriggered())
- underTest.displayView(transferToReceiverSucceeded())
+ getChipbarView().getEndButton().performClick()
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(isClicked).isTrue()
}
@Test
- fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
- underTest.displayView(transferToReceiverTriggered())
+ fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
- transferToReceiverSucceeded(
- object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = null,
+ vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
)
)
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
}
@Test
- fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
- underTest.displayView(transferToReceiverSucceeded())
- underTest.displayView(almostCloseToStartCast())
+ fun updateView_viewUpdated() {
+ // First, display a view
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
- }
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
- @Test
- fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
- underTest.displayView(transferToReceiverTriggered())
- underTest.displayView(transferToReceiverFailed())
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getStartIconView().drawable).isEqualTo(drawable)
+ assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("loadedCD")
+ assertThat(chipbarView.getChipText()).isEqualTo("title text")
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+
+ // WHEN the view is updated
+ val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ underTest.updateView(
+ ChipbarInfo(
+ Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
+ Text.Loaded("new title text"),
+ endItem = ChipbarEndItem.Error,
+ ),
+ chipbarView
+ )
- assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+ // THEN we display the new view
+ assertThat(chipbarView.getStartIconView().drawable).isEqualTo(newDrawable)
+ assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("new CD")
+ assertThat(chipbarView.getChipText()).isEqualTo("new title text")
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
- private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+ private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
(this.requireViewById<TextView>(R.id.text)).text as String
- private fun ViewGroup.getLoadingIconVisibility(): Int =
- this.requireViewById<View>(R.id.loading).visibility
+ private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
- private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
+ private fun ViewGroup.getEndButton(): TextView = this.requireViewById(R.id.end_button)
- private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
+ private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
- private fun getChipView(): ViewGroup {
+ private fun getChipbarView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any())
return viewCaptor.value as ViewGroup
}
-
- // TODO(b/245610654): For now, the below methods are duplicated between this test and
- // [MediaTttSenderCoordinatorTest]. Once we define a generic API for [ChipbarCoordinator],
- // these will no longer be duplicated.
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToStartCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToEndCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
}
-private const val APP_NAME = "Fake app name"
-private const val OTHER_DEVICE_NAME = "My Tablet"
-private const val PACKAGE_NAME = "com.android.systemui"
private const val TIMEOUT = 10000
-
-private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
- .addFeature("feature")
- .setClientPackageName(PACKAGE_NAME)
- .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 10704ac8fc67..17d402319246 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -24,8 +24,8 @@ import android.view.accessibility.AccessibilityManager
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderUiEventLogger
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -39,10 +39,10 @@ class FakeChipbarCoordinator(
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: FalsingManager,
falsingCollector: FalsingCollector,
viewUtil: ViewUtil,
+ vibratorHelper: VibratorHelper,
) :
ChipbarCoordinator(
context,
@@ -52,10 +52,10 @@ class FakeChipbarCoordinator(
accessibilityManager,
configurationController,
powerManager,
- uiEventLogger,
falsingManager,
falsingCollector,
viewUtil,
+ vibratorHelper,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index d951f366c595..525d8371c9ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -110,7 +110,7 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
val thirdExpectedValue =
setUpUsers(
count = 2,
- hasGuest = true,
+ isLastGuestUser = true,
selectedIndex = 1,
)
underTest.refreshUsers()
@@ -121,21 +121,25 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
}
@Test
- fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+ fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
underTest = create(this)
val unsortedUsers =
setUpUsers(
count = 3,
selectedIndex = 0,
+ isLastGuestUser = true,
+ )
+ unsortedUsers[0].creationTime = 999
+ unsortedUsers[1].creationTime = 900
+ unsortedUsers[2].creationTime = 950
+ val expectedUsers =
+ listOf(
+ unsortedUsers[1],
+ unsortedUsers[0],
+ unsortedUsers[2], // last because this is the guest
)
- unsortedUsers[0].creationTime = 900
- unsortedUsers[1].creationTime = 700
- unsortedUsers[2].creationTime = 999
- val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
var userInfos: List<UserInfo>? = null
- var selectedUserInfo: UserInfo? = null
underTest.userInfos.onEach { userInfos = it }.launchIn(this)
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
underTest.refreshUsers()
assertThat(userInfos).isEqualTo(expectedUsers)
@@ -143,14 +147,14 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
private fun setUpUsers(
count: Int,
- hasGuest: Boolean = false,
+ isLastGuestUser: Boolean = false,
selectedIndex: Int = 0,
): List<UserInfo> {
val userInfos =
(0 until count).map { index ->
createUserInfo(
index,
- isGuest = hasGuest && index == count - 1,
+ isGuest = isLastGuestUser && index == count - 1,
)
}
whenever(manager.aliveUsers).thenReturn(userInfos)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index e80d5166d088..f682e31c0547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R.drawable.ic_account_circle
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -317,14 +318,16 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
keyguardRepository.setKeyguardShowing(false)
var dialogRequest: ShowDialogRequestModel? = null
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
- underTest.executeAction(UserActionModel.ADD_USER)
+ underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
assertThat(dialogRequest)
.isEqualTo(
ShowDialogRequestModel.ShowAddUserDialog(
userHandle = userInfos[0].userHandle,
isKeyguardShowing = false,
showEphemeralMessage = false,
+ dialogShower = dialogShower,
)
)
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 09da52e7685c..fa7ebf6a2449 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -80,6 +80,7 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -91,6 +92,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
@@ -190,6 +192,8 @@ public class BubblesTest extends SysuiTestCase {
private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock
private AuthController mAuthController;
+ @Mock
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -290,7 +294,7 @@ public class BubblesTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController);
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
@@ -343,7 +347,8 @@ public class BubblesTest extends SysuiTestCase {
mock(NotificationInterruptLogger.class),
mock(Handler.class),
mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class)
+ mock(KeyguardNotificationVisibilityProvider.class),
+ mock(UiEventLogger.class)
);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 9635faf6e858..e5316bc83a12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -22,6 +22,7 @@ import android.os.Handler;
import android.os.PowerManager;
import android.service.dreams.IDreamManager;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
@@ -46,7 +47,8 @@ public class TestableNotificationInterruptStateProviderImpl
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
super(contentResolver,
powerManager,
dreamManager,
@@ -58,7 +60,8 @@ public class TestableNotificationInterruptStateProviderImpl
logger,
mainHandler,
flags,
- keyguardNotificationVisibilityProvider);
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 5d52be2675e3..a60b7735fbd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -26,7 +26,7 @@ class FakeFeatureFlags : FeatureFlags {
private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
init {
- Flags.getFlagFields().forEach { field ->
+ Flags.flagFields.forEach { field ->
val flag: Flag<*> = field.get(null) as Flag<*>
knownFlagNames[flag.id] = field.name
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 725b1f41372c..0c126805fb78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.StatusBarState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -44,6 +45,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
+ private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
+ override val statusBarState: Flow<StatusBarState> = _statusBarState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 527258579372..c33ce5d9484d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs
-import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,7 +54,7 @@ class FakeFgsManagerController(
override fun init() {}
- override fun showDialog(viewLaunchedFrom: View?) {}
+ override fun showDialog(expandable: Expandable?) {}
override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
numRunningPackagesListeners.add(listener)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 2a9aeddc9aa8..325da4ead666 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineDispatcher
@@ -68,7 +67,6 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
class FooterActionsTestUtils(
private val context: Context,
private val testableLooper: TestableLooper,
- private val fakeClock: FakeSystemClock = FakeSystemClock(),
) {
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a185b585aeda..346fc6c4ab96 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -872,6 +872,33 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
@Override
+ public void setAppWidgetHidden(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "setAppWidgetHidden() " + userId);
+ }
+
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */false);
+
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ try {
+ mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false);
+ } catch (NullPointerException e) {
+ Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e);
+ throw e;
+ }
+ }
+ }
+ }
+
+ @Override
public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
index 38d937288569..37c47baa813b 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -8,14 +8,6 @@
},
{
"name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases"
- },
- {
- "name": "CtsOsTestCases",
- "options": [
- {
- "include-filter": "android.os.cts.CompanionDeviceManagerTest"
- }
- ]
}
]
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0cf79153ce77..1150b83083cf 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3584,6 +3584,13 @@ class StorageManagerService extends IStorageManager.Stub
final boolean includeSharedProfile =
(flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0;
+ // When the caller is the app actually hosting external storage, we
+ // should never attempt to augment the actual storage volume state,
+ // otherwise we risk confusing it with race conditions as users go
+ // through various unlocked states
+ final boolean callerIsMediaStore = UserHandle.isSameApp(callingUid,
+ mMediaStoreAuthorityAppId);
+
// Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile
if (includeSharedProfile) {
try {
@@ -3596,8 +3603,13 @@ class StorageManagerService extends IStorageManager.Stub
// Checking first entry in packagesFromUid is enough as using "sharedUserId"
// mechanism is rare and discouraged. Also, Apps that share same UID share the same
// permissions.
- if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid,
- packagesFromUid[0])) {
+ // Allowing Media Provider is an exception, Media Provider process should be allowed
+ // to query users across profiles, even without MANAGE_EXTERNAL_STORAGE access.
+ // Note that ordinarily Media provider process has the above permission, but if they
+ // are revoked, Storage Volume(s) should still be returned.
+ if (!callerIsMediaStore
+ && !mStorageManagerInternal.hasExternalStorageAccess(callingUid,
+ packagesFromUid[0])) {
throw new SecurityException("Only File Manager Apps permitted");
}
} catch (RemoteException re) {
@@ -3610,13 +3622,6 @@ class StorageManagerService extends IStorageManager.Stub
// point
final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
- // When the caller is the app actually hosting external storage, we
- // should never attempt to augment the actual storage volume state,
- // otherwise we risk confusing it with race conditions as users go
- // through various unlocked states
- final boolean callerIsMediaStore = UserHandle.isSameApp(callingUid,
- mMediaStoreAuthorityAppId);
-
final boolean userIsDemo;
final boolean userKeyUnlocked;
final boolean storagePermission;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f7833b0f36fd..2652ebec5255 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2581,33 +2581,39 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyBarringInfo()")) {
return;
}
- if (barringInfo == null) {
- log("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId);
- mBarringInfo.set(phoneId, new BarringInfo());
+ if (!validatePhoneId(phoneId)) {
+ loge("Received invalid phoneId for BarringInfo = " + phoneId);
return;
}
synchronized (mRecords) {
- if (validatePhoneId(phoneId)) {
- mBarringInfo.set(phoneId, barringInfo);
- // Barring info is non-null
- BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
- if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
- for (Record r : mRecords) {
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_BARRING_INFO_CHANGED)
- && idMatch(r, subId, phoneId)) {
- try {
- if (DBG_LOC) {
- log("notifyBarringInfo: mBarringInfo="
- + barringInfo + " r=" + r);
- }
- r.callback.onBarringInfoChanged(
- checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
- ? barringInfo : biNoLocation);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (barringInfo == null) {
+ loge("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId);
+ mBarringInfo.set(phoneId, new BarringInfo());
+ return;
+ }
+ if (barringInfo.equals(mBarringInfo.get(phoneId))) {
+ if (VDBG) log("Ignoring duplicate barring info.");
+ return;
+ }
+ mBarringInfo.set(phoneId, barringInfo);
+ // Barring info is non-null
+ BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+ if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_BARRING_INFO_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ if (DBG_LOC) {
+ log("notifyBarringInfo: mBarringInfo="
+ + barringInfo + " r=" + r);
}
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7a09109e377b..82af12e2f47f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3967,8 +3967,12 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
+ final boolean hasKillAllPermission = checkCallingPermission(
+ android.Manifest.permission.KILL_ALL_BACKGROUND_PROCESSES) == PERMISSION_GRANTED;
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid,
userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
final int[] userIds = mUserController.expandUserId(userId);
@@ -3983,7 +3987,7 @@ public class ActivityManagerService extends IActivityManager.Stub
targetUserId));
} catch (RemoteException e) {
}
- if (appId == -1) {
+ if (appId == -1 || (!hasKillAllPermission && appId != callingAppId)) {
Slog.w(TAG, "Invalid packageName: " + packageName);
return;
}
@@ -4002,11 +4006,11 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void killAllBackgroundProcesses() {
- if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
+ if (checkCallingPermission(android.Manifest.permission.KILL_ALL_BACKGROUND_PROCESSES)
!= PackageManager.PERMISSION_GRANTED) {
final String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+ + " requires " + android.Manifest.permission.KILL_ALL_BACKGROUND_PROCESSES;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -4042,11 +4046,11 @@ public class ActivityManagerService extends IActivityManager.Stub
* processes, or {@code -1} to ignore the process state
*/
void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState) {
- if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
+ if (checkCallingPermission(android.Manifest.permission.KILL_ALL_BACKGROUND_PROCESSES)
!= PackageManager.PERMISSION_GRANTED) {
final String msg = "Permission Denial: killAllBackgroundProcessesExcept() from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+ + " requires " + android.Manifest.permission.KILL_ALL_BACKGROUND_PROCESSES;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -13373,27 +13377,19 @@ public class ActivityManagerService extends IActivityManager.Stub
int callingPid;
boolean instantApp;
synchronized(this) {
- if (caller != null) {
- callerApp = getRecordForAppLOSP(caller);
- if (callerApp == null) {
- throw new SecurityException(
- "Unable to find app for caller " + caller
- + " (pid=" + Binder.getCallingPid()
- + ") when registering receiver " + receiver);
- }
- if (callerApp.info.uid != SYSTEM_UID
- && !callerApp.getPkgList().containsKey(callerPackage)
- && !"android".equals(callerPackage)) {
- throw new SecurityException("Given caller package " + callerPackage
- + " is not running in process " + callerApp);
- }
- callingUid = callerApp.info.uid;
- callingPid = callerApp.getPid();
- } else {
- callerPackage = null;
- callingUid = Binder.getCallingUid();
- callingPid = Binder.getCallingPid();
+ callerApp = getRecordForAppLOSP(caller);
+ if (callerApp == null) {
+ Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+ return null;
+ }
+ if (callerApp.info.uid != SYSTEM_UID
+ && !callerApp.getPkgList().containsKey(callerPackage)
+ && !"android".equals(callerPackage)) {
+ throw new SecurityException("Given caller package " + callerPackage
+ + " is not running in process " + callerApp);
}
+ callingUid = callerApp.info.uid;
+ callingPid = callerApp.getPid();
instantApp = isInstantApp(callerApp, callerPackage, callingUid);
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
@@ -14700,13 +14696,14 @@ public class ActivityManagerService extends IActivityManager.Stub
// Non-system callers can't declare that a broadcast is alarm-related.
// The PendingIntent invocation case is handled in PendingIntentRecord.
if (bOptions != null && callingUid != SYSTEM_UID) {
- if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+ if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
+ || bOptions.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
if (DEBUG_BROADCAST) {
Slog.w(TAG, "Non-system caller " + callingUid
- + " may not flag broadcast as alarm-related");
+ + " may not flag broadcast as alarm or interactive");
}
throw new SecurityException(
- "Non-system callers may not flag broadcasts as alarm-related");
+ "Non-system callers may not flag broadcasts as alarm or interactive");
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index a4a1c2f0d87c..28a81e6ee145 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -167,7 +167,7 @@ public class BroadcastConstants {
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
- private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = 1_000;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -175,7 +175,7 @@ public class BroadcastConstants {
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
- private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = 10_000;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of complete
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0d6ac1d57387..868c3ae6da50 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -103,6 +103,13 @@ class BroadcastProcessQueue {
private final ArrayDeque<SomeArgs> mPending = new ArrayDeque<>();
/**
+ * Ordered collection of "urgent" broadcasts that are waiting to be
+ * dispatched to this process, in the same representation as
+ * {@link #mPending}.
+ */
+ private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>();
+
+ /**
* Broadcast actively being dispatched to this process.
*/
private @Nullable BroadcastRecord mActive;
@@ -140,12 +147,16 @@ class BroadcastProcessQueue {
private int mCountOrdered;
private int mCountAlarm;
private int mCountPrioritized;
+ private int mCountInteractive;
+ private int mCountResultTo;
+ private int mCountInstrumented;
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
private boolean mProcessCached;
+ private boolean mProcessInstrumented;
private String mCachedToString;
private String mCachedToShortString;
@@ -172,40 +183,65 @@ class BroadcastProcessQueue {
*/
public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
int blockedUntilTerminalCount) {
- // If caller wants to replace, walk backwards looking for any matches
if (record.isReplacePending()) {
- final Iterator<SomeArgs> it = mPending.descendingIterator();
- final Object receiver = record.receivers.get(recordIndex);
- while (it.hasNext()) {
- final SomeArgs args = it.next();
- final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
- final Object testReceiver = testRecord.receivers.get(args.argi1);
- if ((record.callingUid == testRecord.callingUid)
- && (record.userId == testRecord.userId)
- && record.intent.filterEquals(testRecord.intent)
- && isReceiverEquals(receiver, testReceiver)) {
- // Exact match found; perform in-place swap
- args.arg1 = record;
- args.argi1 = recordIndex;
- args.argi2 = blockedUntilTerminalCount;
- onBroadcastDequeued(testRecord);
- onBroadcastEnqueued(record);
- return;
- }
+ boolean didReplace = replaceBroadcastInQueue(mPending,
+ record, recordIndex, blockedUntilTerminalCount)
+ || replaceBroadcastInQueue(mPendingUrgent,
+ record, recordIndex, blockedUntilTerminalCount);
+ if (didReplace) {
+ return;
}
}
// Caller isn't interested in replacing, or we didn't find any pending
// item to replace above, so enqueue as a new broadcast
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = record;
- args.argi1 = recordIndex;
- args.argi2 = blockedUntilTerminalCount;
- mPending.addLast(args);
+ SomeArgs newBroadcastArgs = SomeArgs.obtain();
+ newBroadcastArgs.arg1 = record;
+ newBroadcastArgs.argi1 = recordIndex;
+ newBroadcastArgs.argi2 = blockedUntilTerminalCount;
+
+ // Cross-broadcast prioritization policy: some broadcasts might warrant being
+ // issued ahead of others that are already pending, for example if this new
+ // broadcast is in a different delivery class or is tied to a direct user interaction
+ // with implicit responsiveness expectations.
+ final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
+ queue.addLast(newBroadcastArgs);
onBroadcastEnqueued(record);
}
/**
+ * Searches from newest to oldest, and at the first matching pending broadcast
+ * it finds, replaces it in-place and returns -- does not attempt to handle
+ * "duplicate" broadcasts in the queue.
+ * <p>
+ * @return {@code true} if it found and replaced an existing record in the queue;
+ * {@code false} otherwise.
+ */
+ private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
+ @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+ final Iterator<SomeArgs> it = queue.descendingIterator();
+ final Object receiver = record.receivers.get(recordIndex);
+ while (it.hasNext()) {
+ final SomeArgs args = it.next();
+ final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
+ final Object testReceiver = testRecord.receivers.get(args.argi1);
+ if ((record.callingUid == testRecord.callingUid)
+ && (record.userId == testRecord.userId)
+ && record.intent.filterEquals(testRecord.intent)
+ && isReceiverEquals(receiver, testReceiver)) {
+ // Exact match found; perform in-place swap
+ args.arg1 = record;
+ args.argi1 = recordIndex;
+ args.argi2 = blockedUntilTerminalCount;
+ onBroadcastDequeued(testRecord);
+ onBroadcastEnqueued(record);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Functional interface that tests a {@link BroadcastRecord} that has been
* previously enqueued in {@link BroadcastProcessQueue}.
*/
@@ -233,8 +269,18 @@ class BroadcastProcessQueue {
*/
public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer, boolean andRemove) {
+ boolean didSomething = forEachMatchingBroadcastInQueue(mPending,
+ predicate, consumer, andRemove);
+ didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent,
+ predicate, consumer, andRemove);
+ return didSomething;
+ }
+
+ private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
+ @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
+ boolean andRemove) {
boolean didSomething = false;
- final Iterator<SomeArgs> it = mPending.iterator();
+ final Iterator<SomeArgs> it = queue.iterator();
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
@@ -255,6 +301,18 @@ class BroadcastProcessQueue {
}
/**
+ * Update the actively running "warm" process for this process.
+ */
+ public void setProcess(@Nullable ProcessRecord app) {
+ this.app = app;
+ if (app != null) {
+ setProcessInstrumented(app.getActiveInstrumentation() != null);
+ } else {
+ setProcessInstrumented(false);
+ }
+ }
+
+ /**
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
@@ -266,6 +324,18 @@ class BroadcastProcessQueue {
}
/**
+ * Update if this process is in the "instrumented" state, typically
+ * signaling that broadcast dispatch should bypass all pauses or delays, to
+ * avoid holding up test suites.
+ */
+ public void setProcessInstrumented(boolean instrumented) {
+ if (mProcessInstrumented != instrumented) {
+ mProcessInstrumented = instrumented;
+ invalidateRunnableAt();
+ }
+ }
+
+ /**
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
@@ -273,13 +343,12 @@ class BroadcastProcessQueue {
}
public int getPreferredSchedulingGroupLocked() {
- if (mCountForeground > 0 || mCountOrdered > 0 || mCountAlarm > 0) {
- // We have an important broadcast somewhere down the queue, so
+ if (mCountForeground > 0) {
+ // We have a foreground broadcast somewhere down the queue, so
// boost priority until we drain them all
return ProcessList.SCHED_GROUP_DEFAULT;
- } else if ((mActive != null)
- && (mActive.isForeground() || mActive.ordered || mActive.alarm)) {
- // We have an important broadcast right now, so boost priority
+ } else if ((mActive != null) && mActive.isForeground()) {
+ // We have a foreground broadcast right now, so boost priority
return ProcessList.SCHED_GROUP_DEFAULT;
} else if (!isIdle()) {
return ProcessList.SCHED_GROUP_BACKGROUND;
@@ -309,7 +378,7 @@ class BroadcastProcessQueue {
*/
public void makeActiveNextPending() {
// TODO: what if the next broadcast isn't runnable yet?
- final SomeArgs next = mPending.removeFirst();
+ final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
mActiveBlockedUntilTerminalCount = next.argi2;
@@ -347,6 +416,15 @@ class BroadcastProcessQueue {
if (record.prioritized) {
mCountPrioritized++;
}
+ if (record.interactive) {
+ mCountInteractive++;
+ }
+ if (record.resultTo != null) {
+ mCountResultTo++;
+ }
+ if (record.callerInstrumented) {
+ mCountInstrumented++;
+ }
invalidateRunnableAt();
}
@@ -366,6 +444,15 @@ class BroadcastProcessQueue {
if (record.prioritized) {
mCountPrioritized--;
}
+ if (record.interactive) {
+ mCountInteractive--;
+ }
+ if (record.resultTo != null) {
+ mCountResultTo--;
+ }
+ if (record.callerInstrumented) {
+ mCountInstrumented--;
+ }
invalidateRunnableAt();
}
@@ -413,7 +500,7 @@ class BroadcastProcessQueue {
}
public boolean isEmpty() {
- return mPending.isEmpty();
+ return mPending.isEmpty() && mPendingUrgent.isEmpty();
}
public boolean isActive() {
@@ -421,6 +508,38 @@ class BroadcastProcessQueue {
}
/**
+ * Will thrown an exception if there are no pending broadcasts; relies on
+ * {@link #isEmpty()} being false.
+ */
+ SomeArgs removeNextBroadcast() {
+ ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ return queue.removeFirst();
+ }
+
+ @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
+ if (!mPendingUrgent.isEmpty()) {
+ return mPendingUrgent;
+ } else if (!mPending.isEmpty()) {
+ return mPending;
+ }
+ return null;
+ }
+
+ /**
+ * Returns null if there are no pending broadcasts
+ */
+ @Nullable SomeArgs peekNextBroadcast() {
+ ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ return (queue != null) ? queue.peekFirst() : null;
+ }
+
+ @VisibleForTesting
+ @Nullable BroadcastRecord peekNextBroadcastRecord() {
+ ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+ }
+
+ /**
* Quickly determine if this queue has broadcasts that are still waiting to
* be delivered at some point in the future.
*/
@@ -437,11 +556,13 @@ class BroadcastProcessQueue {
return mActive.enqueueTime > barrierTime;
}
final SomeArgs next = mPending.peekFirst();
- if (next != null) {
- return ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
- }
- // Nothing running or runnable means we're past the barrier
- return true;
+ final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
+ // Empty queue is past any barrier
+ final boolean nextLater = next == null
+ || ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
+ final boolean nextUrgentLater = nextUrgent == null
+ || ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
+ return nextLater && nextUrgentLater;
}
public boolean isRunnable() {
@@ -477,25 +598,33 @@ class BroadcastProcessQueue {
}
static final int REASON_EMPTY = 0;
- static final int REASON_CONTAINS_FOREGROUND = 1;
- static final int REASON_CONTAINS_ORDERED = 2;
- static final int REASON_CONTAINS_ALARM = 3;
- static final int REASON_CONTAINS_PRIORITIZED = 4;
- static final int REASON_CACHED = 5;
- static final int REASON_NORMAL = 6;
- static final int REASON_MAX_PENDING = 7;
- static final int REASON_BLOCKED = 8;
+ static final int REASON_CACHED = 1;
+ static final int REASON_NORMAL = 2;
+ static final int REASON_MAX_PENDING = 3;
+ static final int REASON_BLOCKED = 4;
+ static final int REASON_INSTRUMENTED = 5;
+ static final int REASON_CONTAINS_FOREGROUND = 10;
+ static final int REASON_CONTAINS_ORDERED = 11;
+ static final int REASON_CONTAINS_ALARM = 12;
+ static final int REASON_CONTAINS_PRIORITIZED = 13;
+ static final int REASON_CONTAINS_INTERACTIVE = 14;
+ static final int REASON_CONTAINS_RESULT_TO = 15;
+ static final int REASON_CONTAINS_INSTRUMENTED = 16;
@IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY,
- REASON_CONTAINS_FOREGROUND,
- REASON_CONTAINS_ORDERED,
- REASON_CONTAINS_ALARM,
- REASON_CONTAINS_PRIORITIZED,
REASON_CACHED,
REASON_NORMAL,
REASON_MAX_PENDING,
REASON_BLOCKED,
+ REASON_INSTRUMENTED,
+ REASON_CONTAINS_FOREGROUND,
+ REASON_CONTAINS_ORDERED,
+ REASON_CONTAINS_ALARM,
+ REASON_CONTAINS_PRIORITIZED,
+ REASON_CONTAINS_INTERACTIVE,
+ REASON_CONTAINS_RESULT_TO,
+ REASON_CONTAINS_INSTRUMENTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -503,14 +632,18 @@ class BroadcastProcessQueue {
static @NonNull String reasonToString(@Reason int reason) {
switch (reason) {
case REASON_EMPTY: return "EMPTY";
- case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
- case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
- case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
- case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED";
case REASON_CACHED: return "CACHED";
case REASON_NORMAL: return "NORMAL";
case REASON_MAX_PENDING: return "MAX_PENDING";
case REASON_BLOCKED: return "BLOCKED";
+ case REASON_INSTRUMENTED: return "INSTRUMENTED";
+ case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
+ case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
+ case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
+ case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED";
+ case REASON_CONTAINS_INTERACTIVE: return "CONTAINS_INTERACTIVE";
+ case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
+ case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
default: return Integer.toString(reason);
}
}
@@ -519,7 +652,7 @@ class BroadcastProcessQueue {
* Update {@link #getRunnableAt()} if it's currently invalidated.
*/
private void updateRunnableAt() {
- final SomeArgs next = mPending.peekFirst();
+ final SomeArgs next = peekNextBroadcast();
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
@@ -537,7 +670,7 @@ class BroadcastProcessQueue {
// If we have too many broadcasts pending, bypass any delays that
// might have been applied above to aid draining
- if (mPending.size() >= constants.MAX_PENDING_BROADCASTS) {
+ if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_MAX_PENDING;
return;
@@ -555,6 +688,18 @@ class BroadcastProcessQueue {
} else if (mCountPrioritized > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
+ } else if (mCountInteractive > 0) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_INTERACTIVE;
+ } else if (mCountResultTo > 0) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+ } else if (mCountInstrumented > 0) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_INSTRUMENTED;
+ } else if (mProcessInstrumented) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_INSTRUMENTED;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
@@ -574,8 +719,8 @@ class BroadcastProcessQueue {
*/
public void checkHealthLocked() {
if (mRunnableAtReason == REASON_BLOCKED) {
- final SomeArgs next = mPending.peekFirst();
- Objects.requireNonNull(next, "peekFirst");
+ final SomeArgs next = peekNextBroadcast();
+ Objects.requireNonNull(next, "peekNextBroadcast");
// If blocked more than 10 minutes, we're likely wedged
final BroadcastRecord r = (BroadcastRecord) next.arg1;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e421c61a2bd6..db3ef3d51b16 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -441,7 +441,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// relevant per-process queue
final BroadcastProcessQueue queue = getProcessQueue(app);
if (queue != null) {
- queue.app = app;
+ queue.setProcess(app);
}
boolean didSomething = false;
@@ -478,7 +478,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// relevant per-process queue
final BroadcastProcessQueue queue = getProcessQueue(app);
if (queue != null) {
- queue.app = null;
+ queue.setProcess(null);
}
if ((mRunningColdStart != null) && (mRunningColdStart == queue)) {
@@ -816,19 +816,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
final BroadcastRecord r = queue.getActive();
- r.resultCode = resultCode;
- r.resultData = resultData;
- r.resultExtras = resultExtras;
- if (!r.isNoAbort()) {
- r.resultAbort = resultAbort;
- }
-
- // When the caller aborted an ordered broadcast, we mark all remaining
- // receivers as skipped
- if (r.ordered && r.resultAbort) {
- for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
- setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED);
+ if (r.ordered) {
+ r.resultCode = resultCode;
+ r.resultData = resultData;
+ r.resultExtras = resultExtras;
+ if (!r.isNoAbort()) {
+ r.resultAbort = resultAbort;
+ }
+
+ // When the caller aborted an ordered broadcast, we mark all
+ // remaining receivers as skipped
+ if (r.resultAbort) {
+ for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
+ setDeliveryState(null, null, r, i, r.receivers.get(i),
+ BroadcastRecord.DELIVERY_SKIPPED);
+ }
}
}
@@ -925,7 +927,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
notifyFinishReceiver(queue, r, index, receiver);
// When entire ordered broadcast finished, deliver final result
- if (r.ordered && (r.terminalCount == r.receivers.size())) {
+ final boolean recordFinished = (r.terminalCount == r.receivers.size());
+ if (recordFinished) {
scheduleResultTo(r);
}
@@ -1217,7 +1220,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
- queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
+ queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid));
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 4f640033d1a4..d7dc8b80931b 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -78,11 +78,13 @@ final class BroadcastRecord extends Binder {
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
final boolean callerInstantApp; // caller is an Instant App?
+ final boolean callerInstrumented; // caller is being instrumented
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
final boolean pushMessage; // originated from a push message?
final boolean pushMessageOverQuota; // originated from a push message which was over quota?
+ final boolean interactive; // originated from user interaction?
final boolean initialSticky; // initial broadcast from register to sticky?
final boolean prioritized; // contains more than one priority tranche
final int userId; // user id this broadcast was for
@@ -364,6 +366,8 @@ final class BroadcastRecord extends Binder {
callingPid = _callingPid;
callingUid = _callingUid;
callerInstantApp = _callerInstantApp;
+ callerInstrumented = (_callerApp != null)
+ ? (_callerApp.getActiveInstrumentation() != null) : false;
resolvedType = _resolvedType;
requiredPermissions = _requiredPermissions;
excludedPermissions = _excludedPermissions;
@@ -392,6 +396,7 @@ final class BroadcastRecord extends Binder {
alarm = options != null && options.isAlarmBroadcast();
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
+ interactive = options != null && options.isInteractiveBroadcast();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
@@ -409,6 +414,7 @@ final class BroadcastRecord extends Binder {
callingPid = from.callingPid;
callingUid = from.callingUid;
callerInstantApp = from.callerInstantApp;
+ callerInstrumented = from.callerInstrumented;
ordered = from.ordered;
sticky = from.sticky;
initialSticky = from.initialSticky;
@@ -450,6 +456,7 @@ final class BroadcastRecord extends Binder {
alarm = from.alarm;
pushMessage = from.pushMessage;
pushMessageOverQuota = from.pushMessageOverQuota;
+ interactive = from.interactive;
filterExtrasForReceiver = from.filterExtrasForReceiver;
}
@@ -611,6 +618,18 @@ final class BroadcastRecord extends Binder {
return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
}
+ /**
+ * Core policy determination about this broadcast's delivery prioritization
+ */
+ boolean isUrgent() {
+ // TODO: flags for controlling policy
+ // TODO: migrate alarm-prioritization flag to BroadcastConstants
+ return (isForeground()
+ || interactive
+ || alarm)
+ && receivers.size() == 1;
+ }
+
@NonNull String getHostingRecordTriggerType() {
if (alarm) {
return HostingRecord.TRIGGER_TYPE_ALARM;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 975619feaea0..740efbc658ba 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -443,13 +443,14 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
// invocation side effects such as allowlisting.
if (options != null && callingUid != Process.SYSTEM_UID
&& key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
- if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+ if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
+ || options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
if (DEBUG_BROADCAST_LIGHT) {
Slog.w(TAG, "Non-system caller " + callingUid
- + " may not flag broadcast as alarm-related");
+ + " may not flag broadcast as alarm or interactive");
}
throw new SecurityException(
- "Non-system callers may not flag broadcasts as alarm-related");
+ "Non-system callers may not flag broadcasts as alarm or interactive");
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 1370fd83f6a8..da7781add8c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -21,6 +21,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -293,4 +294,30 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient {
+ ", requestId=" + getRequestId()
+ ", userId=" + getTargetUserId() + "}";
}
+
+ /**
+ * Cancels this ClientMonitor
+ */
+ public void cancel() {
+ cancelWithoutStarting(mCallback);
+ }
+
+ /**
+ * Cancels this ClientMonitor without starting
+ * @param callback
+ */
+ public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) {
+ Slog.d(TAG, "cancelWithoutStarting: " + this);
+
+ final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+ try {
+ ClientMonitorCallbackConverter listener = getListener();
+ if (listener != null) {
+ listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendError", e);
+ }
+ callback.onClientFinished(this, true /* success */);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 9317c4ec12b5..fb978b2ba4b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -543,4 +543,37 @@ public class BiometricScheduler {
mPendingOperations.clear();
mCurrentOperation = null;
}
+
+ /**
+ * Marks all pending operations as canceling and cancels the current
+ * operation.
+ */
+ private void clearScheduler() {
+ if (mCurrentOperation == null) {
+ return;
+ }
+ for (BiometricSchedulerOperation pendingOperation : mPendingOperations) {
+ Slog.d(getTag(), "[Watchdog cancelling pending] "
+ + pendingOperation.getClientMonitor());
+ pendingOperation.markCanceling();
+ }
+ Slog.d(getTag(), "[Watchdog cancelling current] "
+ + mCurrentOperation.getClientMonitor());
+ mCurrentOperation.cancel(mHandler, getInternalCallback());
+ }
+
+ /**
+ * Start the timeout for the watchdog.
+ */
+ public void startWatchdog() {
+ if (mCurrentOperation == null) {
+ return;
+ }
+ final BiometricSchedulerOperation mOperation = mCurrentOperation;
+ mHandler.postDelayed(() -> {
+ if (mOperation == mCurrentOperation) {
+ clearScheduler();
+ }
+ }, 10000);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index ef2931ff5850..dacec38b0e7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -267,7 +267,7 @@ public class BiometricSchedulerOperation {
/** Flags this operation as canceled, if possible, but does not cancel it until started. */
public boolean markCanceling() {
- if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
+ if (mState == STATE_WAITING_IN_QUEUE) {
mState = STATE_WAITING_IN_QUEUE_CANCELING;
return true;
}
@@ -287,10 +287,6 @@ public class BiometricSchedulerOperation {
}
final int currentState = mState;
- if (!isInterruptable()) {
- Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this);
- return;
- }
if (currentState == STATE_STARTED_CANCELING) {
Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
return;
@@ -301,10 +297,10 @@ public class BiometricSchedulerOperation {
|| currentState == STATE_WAITING_IN_QUEUE_CANCELING
|| currentState == STATE_WAITING_FOR_COOKIE) {
Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
- ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback));
+ mClientMonitor.cancelWithoutStarting(getWrappedCallback(callback));
} else {
Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
- ((Interruptable) mClientMonitor).cancel();
+ mClientMonitor.cancel();
}
// forcibly finish this client if the HAL does not acknowledge within the timeout
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 271bce9890c6..2761ec04aa7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -183,6 +183,18 @@ public class FaceService extends SystemService {
receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void scheduleWatchdog() {
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for scheduling watchdog");
+ return;
+ }
+
+ provider.second.scheduleWatchdog(provider.first);
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 4efaedbd5530..85f95cec8377 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -128,4 +128,10 @@ public interface ServiceProvider extends BiometricServiceProvider<FaceSensorProp
@NonNull String opPackageName);
void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args);
+
+ /**
+ * Schedules watchdog for canceling hung operations
+ * @param sensorId sensor ID of the associated operation
+ */
+ default void scheduleWatchdog(int sensorId) {}
}
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 b60f9d80d425..c12994c993e6 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
@@ -52,6 +52,7 @@ import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
@@ -661,4 +662,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
void setTestHalEnabled(boolean enabled) {
mTestHalEnabled = enabled;
}
+
+ @Override
+ public void scheduleWatchdog(int sensorId) {
+ Slog.d(getTag(), "Starting watchdog for face");
+ final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+ if (biometricScheduler == null) {
+ return;
+ }
+ biometricScheduler.startWatchdog();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 7e2742edd47a..b0dc28ddce96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -879,6 +879,18 @@ public class FingerprintService extends SystemService {
provider.onPowerPressed();
}
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void scheduleWatchdog() {
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for scheduling watchdog");
+ return;
+ }
+
+ provider.second.scheduleWatchdog(provider.first);
+ }
};
public FingerprintService(Context context) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 9075e7ec2080..0c29f5615b4c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -140,4 +140,10 @@ public interface ServiceProvider extends
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
+
+ /**
+ * Schedules watchdog for canceling hung operations
+ * @param sensorId sensor ID of the associated operation
+ */
+ default void scheduleWatchdog(int sensorId) {}
}
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 650894db431a..17ba07f2c2bd 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
@@ -59,6 +59,7 @@ import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
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;
@@ -779,4 +780,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
return null;
}
+
+ @Override
+ public void scheduleWatchdog(int sensorId) {
+ Slog.d(getTag(), "Starting watchdog for fingerprint");
+ final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+ if (biometricScheduler == null) {
+ return;
+ }
+ biometricScheduler.startWatchdog();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 587db41c0df8..5eb15e09f09e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -40,6 +40,7 @@ import static android.os.Process.ROOT_UID;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.compat.CompatChanges;
@@ -101,6 +102,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.text.TextUtils;
@@ -202,8 +204,6 @@ public final class DisplayManagerService extends SystemService {
private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
- private static final String PROP_USE_NEW_DISPLAY_POWER_CONTROLLER =
- "persist.sys.use_new_display_power_controller";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
// This value needs to be in sync with the threshold
// in RefreshRateConfigs::getFrameRateDivisor.
@@ -1356,11 +1356,19 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- final int displayId = createVirtualDisplayLocked(callback, projection, callingUid,
- packageName, surface, flags, virtualDisplayConfig);
+ final int displayId =
+ createVirtualDisplayLocked(
+ callback,
+ projection,
+ callingUid,
+ packageName,
+ virtualDevice,
+ surface,
+ flags,
+ virtualDisplayConfig);
if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
- mDisplayWindowPolicyControllers.put(displayId,
- Pair.create(virtualDevice, dwpc));
+ mDisplayWindowPolicyControllers.put(
+ displayId, Pair.create(virtualDevice, dwpc));
}
return displayId;
}
@@ -1369,12 +1377,20 @@ public final class DisplayManagerService extends SystemService {
}
}
- private int createVirtualDisplayLocked(IVirtualDisplayCallback callback,
- IMediaProjection projection, int callingUid, String packageName, Surface surface,
- int flags, VirtualDisplayConfig virtualDisplayConfig) {
+ private int createVirtualDisplayLocked(
+ IVirtualDisplayCallback callback,
+ IMediaProjection projection,
+ int callingUid,
+ String packageName,
+ IVirtualDevice virtualDevice,
+ Surface surface,
+ int flags,
+ VirtualDisplayConfig virtualDisplayConfig) {
if (mVirtualDisplayAdapter == null) {
- Slog.w(TAG, "Rejecting request to create private virtual display "
- + "because the virtual display adapter is not available.");
+ Slog.w(
+ TAG,
+ "Rejecting request to create private virtual display "
+ + "because the virtual display adapter is not available.");
return -1;
}
@@ -1385,6 +1401,19 @@ public final class DisplayManagerService extends SystemService {
return -1;
}
+ // If the display is to be added to a device display group, we need to make the
+ // LogicalDisplayMapper aware of the link between the new display and its associated virtual
+ // device before triggering DISPLAY_DEVICE_EVENT_ADDED.
+ if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0) {
+ try {
+ final int virtualDeviceId = virtualDevice.getDeviceId();
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
+ device, virtualDeviceId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
// DisplayDevice events are handled manually for Virtual Displays.
// TODO: multi-display Fix this so that generic add/remove events are not handled in a
// different code path for virtual displays. Currently this happens so that we can
@@ -1393,8 +1422,7 @@ public final class DisplayManagerService extends SystemService {
// called on the DisplayThread (which we don't want to wait for?).
// One option would be to actually wait here on the binder thread
// to be notified when the virtual display is created (or failed).
- mDisplayDeviceRepo.onDisplayDeviceEvent(device,
- DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display != null) {
@@ -2575,6 +2603,7 @@ public final class DisplayManagerService extends SystemService {
mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
}
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
private void addDisplayPowerControllerLocked(LogicalDisplay display) {
if (mPowerHandler == null) {
// initPowerManagement has not yet been called.
@@ -2588,7 +2617,8 @@ public final class DisplayManagerService extends SystemService {
display, mSyncRoot);
final DisplayPowerControllerInterface displayPowerController;
- if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) {
+ if (DeviceConfig.getBoolean("display_manager",
+ "use_newly_structured_display_power_controller", false)) {
displayPowerController = new DisplayPowerController2(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 70c9e23c6af8..cb97e2832854 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -28,6 +28,7 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -123,6 +124,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
/** Map of all display groups indexed by display group id. */
private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
+ /**
+ * Map of display groups which are linked to virtual devices (all displays in the group are
+ * linked to that device). Keyed by virtual device unique id.
+ */
+ private final SparseIntArray mDeviceDisplayGroupIds = new SparseIntArray();
+
private final DisplayDeviceRepository mDisplayDeviceRepo;
private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener;
@@ -157,6 +164,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
*/
private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray();
+ /**
+ * ArrayMap of display device unique ID to virtual device ID. Used in {@link
+ * #updateLogicalDisplaysLocked} to establish which Virtual Devices own which Virtual Displays.
+ */
+ private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
+
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
@@ -362,6 +375,19 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mDeviceStateToLayoutMap.dumpLocked(ipw);
}
+ /**
+ * Creates an association between a displayDevice and a virtual device. Any displays associated
+ * with this virtual device will be grouped together in a single {@link DisplayGroup} unless
+ * created with {@link Display.FLAG_OWN_DISPLAY_GROUP}.
+ *
+ * @param displayDevice the displayDevice to be linked
+ * @param virtualDeviceUniqueId the unique ID of the virtual device.
+ */
+ void associateDisplayDeviceWithVirtualDevice(
+ DisplayDevice displayDevice, int virtualDeviceUniqueId) {
+ mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId);
+ }
+
void setDeviceStateLocked(int state, boolean isOverrideActive) {
Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+ ", interactive=" + mInteractive);
@@ -556,6 +582,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
+ // Remove any virtual device mapping which exists for the display.
+ mVirtualDeviceDisplayMapping.remove(device.getUniqueId());
+
if (layoutDisplay.getAddress().equals(deviceInfo.address)) {
layout.removeDisplayLocked(DEFAULT_DISPLAY);
@@ -749,24 +778,44 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// We wait until we sent the EVENT_REMOVED event before actually removing the
// group.
mDisplayGroups.delete(id);
+ // Remove possible reference to the removed group.
+ int deviceIndex = mDeviceDisplayGroupIds.indexOfValue(id);
+ if (deviceIndex >= 0) {
+ mDeviceDisplayGroupIds.removeAt(deviceIndex);
+ }
}
}
}
private void assignDisplayGroupLocked(LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
+ final String primaryDisplayUniqueId = display.getPrimaryDisplayDeviceLocked().getUniqueId();
+ final Integer linkedDeviceUniqueId =
+ mVirtualDeviceDisplayMapping.get(primaryDisplayUniqueId);
// Get current display group data
int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId);
+ Integer deviceDisplayGroupId = null;
+ if (linkedDeviceUniqueId != null
+ && mDeviceDisplayGroupIds.indexOfKey(linkedDeviceUniqueId) > 0) {
+ deviceDisplayGroupId = mDeviceDisplayGroupIds.get(linkedDeviceUniqueId);
+ }
final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);
// Get the new display group if a change is needed
final DisplayInfo info = display.getDisplayInfoLocked();
final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0;
final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP;
+ final boolean needsDeviceDisplayGroup =
+ !needsOwnDisplayGroup && linkedDeviceUniqueId != null;
+ final boolean hasDeviceDisplayGroup =
+ deviceDisplayGroupId != null && groupId == deviceDisplayGroupId;
if (groupId == Display.INVALID_DISPLAY_GROUP
- || hasOwnDisplayGroup != needsOwnDisplayGroup) {
- groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup);
+ || hasOwnDisplayGroup != needsOwnDisplayGroup
+ || hasDeviceDisplayGroup != needsDeviceDisplayGroup) {
+ groupId =
+ assignDisplayGroupIdLocked(
+ needsOwnDisplayGroup, needsDeviceDisplayGroup, linkedDeviceUniqueId);
}
// Create a new group if needed
@@ -931,7 +980,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
display.setPhase(phase);
}
- private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
+ private int assignDisplayGroupIdLocked(
+ boolean isOwnDisplayGroup, boolean isDeviceDisplayGroup, Integer linkedDeviceUniqueId) {
+ if (isDeviceDisplayGroup && linkedDeviceUniqueId != null) {
+ int deviceDisplayGroupId = mDeviceDisplayGroupIds.get(linkedDeviceUniqueId);
+ // A value of 0 indicates that no device display group was found.
+ if (deviceDisplayGroupId == 0) {
+ deviceDisplayGroupId = mNextNonDefaultGroupId++;
+ mDeviceDisplayGroupIds.put(linkedDeviceUniqueId, deviceDisplayGroupId);
+ }
+ return deviceDisplayGroupId;
+ }
return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP;
}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 819b719dd22e..cd9ef0915741 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -42,6 +42,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.NoSuchElementException;
/**
@@ -62,8 +64,6 @@ final class DreamController {
private final Handler mHandler;
private final Listener mListener;
private final ActivityTaskManager mActivityTaskManager;
- private long mDreamStartTime;
- private String mSavedStopReason;
private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -74,21 +74,15 @@ final class DreamController {
private DreamRecord mCurrentDream;
- private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
- @Override
- public void run() {
- if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
- Slog.w(TAG, "Bound dream did not connect in the time allotted");
- stopDream(true /*immediate*/, "slow to connect");
- }
- }
- };
+ // Whether a dreaming started intent has been broadcast.
+ private boolean mSentStartBroadcast = false;
- private final Runnable mStopStubbornDreamRunnable = () -> {
- Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
- stopDream(true /*immediate*/, "slow to finish");
- mSavedStopReason = null;
- };
+ // When a new dream is started and there is an existing dream, the existing dream is allowed to
+ // live a little longer until the new dream is started, for a smoother transition. This dream is
+ // stopped as soon as the new dream is started, and this list is cleared. Usually there should
+ // only be one previous dream while waiting for a new dream to start, but we store a list to
+ // proof the edge case of multiple previous dreams.
+ private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>();
public DreamController(Context context, Handler handler, Listener listener) {
mContext = context;
@@ -110,18 +104,17 @@ final class DreamController {
pw.println(" mUserId=" + mCurrentDream.mUserId);
pw.println(" mBound=" + mCurrentDream.mBound);
pw.println(" mService=" + mCurrentDream.mService);
- pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
pw.println(" mWakingGently=" + mCurrentDream.mWakingGently);
} else {
pw.println(" mCurrentDream: null");
}
+
+ pw.println(" mSentStartBroadcast=" + mSentStartBroadcast);
}
public void startDream(Binder token, ComponentName name,
boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
ComponentName overlayComponentName, String reason) {
- stopDream(true /*immediate*/, "starting new dream");
-
Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
try {
// Close the notification shade. No need to send to all, but better to be explicit.
@@ -131,9 +124,12 @@ final class DreamController {
+ ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
+ ", userId=" + userId + ", reason='" + reason + "'");
+ if (mCurrentDream != null) {
+ mPreviousDreams.add(mCurrentDream);
+ }
mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
- mDreamStartTime = SystemClock.elapsedRealtime();
+ mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime();
MetricsLogger.visible(mContext,
mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
@@ -156,31 +152,49 @@ final class DreamController {
}
mCurrentDream.mBound = true;
- mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
+ mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable,
+ DREAM_CONNECTION_TIMEOUT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+ /**
+ * Stops dreaming.
+ *
+ * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
+ * dreaming.
+ */
public void stopDream(boolean immediate, String reason) {
- if (mCurrentDream == null) {
+ stopPreviousDreams();
+ stopDreamInstance(immediate, reason, mCurrentDream);
+ }
+
+ /**
+ * Stops the given dream instance.
+ *
+ * The device may still be dreaming afterwards if there are other dreams running.
+ */
+ private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) {
+ if (dream == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
try {
if (!immediate) {
- if (mCurrentDream.mWakingGently) {
+ if (dream.mWakingGently) {
return; // already waking gently
}
- if (mCurrentDream.mService != null) {
+ if (dream.mService != null) {
// Give the dream a moment to wake up and finish itself gently.
- mCurrentDream.mWakingGently = true;
+ dream.mWakingGently = true;
try {
- mSavedStopReason = reason;
- mCurrentDream.mService.wakeUp();
- mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
+ dream.mStopReason = reason;
+ dream.mService.wakeUp();
+ mHandler.postDelayed(dream.mStopStubbornDreamRunnable,
+ DREAM_FINISH_TIMEOUT);
return;
} catch (RemoteException ex) {
// oh well, we tried, finish immediately instead
@@ -188,56 +202,76 @@ final class DreamController {
}
}
- final DreamRecord oldDream = mCurrentDream;
- mCurrentDream = null;
- Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
- + ", isPreviewMode=" + oldDream.mIsPreviewMode
- + ", canDoze=" + oldDream.mCanDoze
- + ", userId=" + oldDream.mUserId
+ Slog.i(TAG, "Stopping dream: name=" + dream.mName
+ + ", isPreviewMode=" + dream.mIsPreviewMode
+ + ", canDoze=" + dream.mCanDoze
+ + ", userId=" + dream.mUserId
+ ", reason='" + reason + "'"
- + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')"));
+ + (dream.mStopReason == null ? "" : "(from '"
+ + dream.mStopReason + "')"));
MetricsLogger.hidden(mContext,
- oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
+ dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
MetricsLogger.histogram(mContext,
- oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" ,
- (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L)));
+ dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes",
+ (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L
+ * 60L)));
- mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
- mHandler.removeCallbacks(mStopStubbornDreamRunnable);
- mSavedStopReason = null;
+ mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable);
+ mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable);
- if (oldDream.mSentStartBroadcast) {
- mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
- }
-
- if (oldDream.mService != null) {
+ if (dream.mService != null) {
try {
- oldDream.mService.detach();
+ dream.mService.detach();
} catch (RemoteException ex) {
// we don't care; this thing is on the way out
}
try {
- oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
+ dream.mService.asBinder().unlinkToDeath(dream, 0);
} catch (NoSuchElementException ex) {
// don't care
}
- oldDream.mService = null;
+ dream.mService = null;
}
- if (oldDream.mBound) {
- mContext.unbindService(oldDream);
+ if (dream.mBound) {
+ mContext.unbindService(dream);
}
- oldDream.releaseWakeLockIfNeeded();
+ dream.releaseWakeLockIfNeeded();
+
+ // Current dream stopped, device no longer dreaming.
+ if (dream == mCurrentDream) {
+ mCurrentDream = null;
+
+ if (mSentStartBroadcast) {
+ mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+ }
- mActivityTaskManager.removeRootTasksWithActivityTypes(new int[] {ACTIVITY_TYPE_DREAM});
+ mActivityTaskManager.removeRootTasksWithActivityTypes(
+ new int[] {ACTIVITY_TYPE_DREAM});
- mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));
+ mListener.onDreamStopped(dream.mToken);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+ /**
+ * Stops all previous dreams, if any.
+ */
+ private void stopPreviousDreams() {
+ if (mPreviousDreams.isEmpty()) {
+ return;
+ }
+
+ // Using an iterator because mPreviousDreams is modified while the iteration is in process.
+ for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) {
+ stopDreamInstance(true /*immediate*/, "stop previous dream", it.next());
+ it.remove();
+ }
+ }
+
private void attach(IDreamService service) {
try {
service.asBinder().linkToDeath(mCurrentDream, 0);
@@ -251,9 +285,9 @@ final class DreamController {
mCurrentDream.mService = service;
- if (!mCurrentDream.mIsPreviewMode) {
+ if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) {
mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
- mCurrentDream.mSentStartBroadcast = true;
+ mSentStartBroadcast = true;
}
}
@@ -275,10 +309,35 @@ final class DreamController {
public boolean mBound;
public boolean mConnected;
public IDreamService mService;
- public boolean mSentStartBroadcast;
-
+ private String mStopReason;
+ private long mDreamStartTime;
public boolean mWakingGently;
+ private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
+ private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
+
+ private final Runnable mStopUnconnectedDreamRunnable = () -> {
+ if (mBound && !mConnected) {
+ Slog.w(TAG, "Bound dream did not connect in the time allotted");
+ stopDream(true /*immediate*/, "slow to connect" /*reason*/);
+ }
+ };
+
+ private final Runnable mStopStubbornDreamRunnable = () -> {
+ Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
+ stopDream(true /*immediate*/, "slow to finish" /*reason*/);
+ mStopReason = null;
+ };
+
+ private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
+ // May be called on any thread.
+ @Override
+ public void sendResult(Bundle data) {
+ mHandler.post(mStopPreviousDreamsIfNeeded);
+ mHandler.post(mReleaseWakeLockIfNeeded);
+ }
+ };
+
DreamRecord(Binder token, ComponentName name, boolean isPreviewMode,
boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
mToken = token;
@@ -289,7 +348,9 @@ final class DreamController {
mWakeLock = wakeLock;
// Hold the lock while we're waiting for the service to connect and start dreaming.
// Released after the service has started dreaming, we stop dreaming, or it timed out.
- mWakeLock.acquire();
+ if (mWakeLock != null) {
+ mWakeLock.acquire();
+ }
mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000);
}
@@ -329,6 +390,12 @@ final class DreamController {
});
}
+ void stopPreviousDreamsIfNeeded() {
+ if (mCurrentDream == DreamRecord.this) {
+ stopPreviousDreams();
+ }
+ }
+
void releaseWakeLockIfNeeded() {
if (mWakeLock != null) {
mWakeLock.release();
@@ -336,15 +403,5 @@ final class DreamController {
mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
}
}
-
- final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
-
- final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
- // May be called on any thread.
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(mReleaseWakeLockIfNeeded);
- }
- };
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 951a8a249f87..6e2ccebb6ff4 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -493,8 +493,6 @@ public final class DreamManagerService extends SystemService {
return;
}
- stopDreamLocked(true /*immediate*/, "starting new dream");
-
Slog.i(TAG, "Entering dreamland.");
mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index 5253d34a38f0..d4e8f27a7b34 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -19,28 +19,9 @@ import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
/**
* Gets the service name using a framework resources, temporarily changing the service if necessary
@@ -48,259 +29,42 @@ import java.util.List;
*
* @hide
*/
-public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {
-
- private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
-
- /** Handler message to {@link #resetTemporaryService(int)} */
- private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {
- @NonNull
- private final Context mContext;
- @NonNull
- private final Object mLock = new Object();
- @StringRes
private final int mStringResourceId;
@ArrayRes
private final int mArrayResourceId;
- private final boolean mIsMultiple;
- /**
- * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
- * keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
- * mIsMultiple is true.
- */
- @GuardedBy("mLock")
- private final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
- /**
- * Map of default services that have been disabled by
- * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests.
- */
- @GuardedBy("mLock")
- private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
- @Nullable
- private NameResolverListener mOnSetCallback;
- /**
- * When the temporary service will expire (and reset back to the default).
- */
- @GuardedBy("mLock")
- private long mTemporaryServiceExpiration;
-
- /**
- * Handler used to reset the temporary service name.
- */
- @GuardedBy("mLock")
- private Handler mTemporaryHandler;
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@StringRes int resourceId) {
- mContext = context;
+ super(context, false);
mStringResourceId = resourceId;
mArrayResourceId = -1;
- mIsMultiple = false;
}
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@ArrayRes int resourceId, boolean isMultiple) {
+ super(context, isMultiple);
if (!isMultiple) {
throw new UnsupportedOperationException("Please use "
+ "FrameworkResourcesServiceNameResolver(context, @StringRes int) constructor "
+ "if single service mode is requested.");
}
- mContext = context;
mStringResourceId = -1;
mArrayResourceId = resourceId;
- mIsMultiple = true;
- }
-
- @Override
- public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
- synchronized (mLock) {
- this.mOnSetCallback = callback;
- }
- }
-
- @Override
- public String getServiceName(@UserIdInt int userId) {
- String[] serviceNames = getServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
- }
-
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- String[] serviceNames = getDefaultServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getServiceNameList(int userId) {
- synchronized (mLock) {
- String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): using temporary name "
- + Arrays.toString(temporaryNames) + " for user " + userId);
- return temporaryNames;
- }
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- if (disabled) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
- + "user " + userId);
- return null;
- }
- return getDefaultServiceNameList(userId);
-
- }
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getDefaultServiceNameList(int userId) {
- synchronized (mLock) {
- if (mIsMultiple) {
- String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId);
- // Filter out unimplemented services
- // Initialize the validated array as null because we do not know the final size.
- List<String> validatedServiceNameList = new ArrayList<>();
- try {
- for (int i = 0; i < serviceNameList.length; i++) {
- if (TextUtils.isEmpty(serviceNameList[i])) {
- continue;
- }
- ComponentName serviceComponent = ComponentName.unflattenFromString(
- serviceNameList[i]);
- ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- if (serviceInfo != null) {
- validatedServiceNameList.add(serviceNameList[i]);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Could not validate provided services.", e);
- }
- String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
- return validatedServiceNameList.toArray(validatedServiceNameArray);
- } else {
- final String name = mContext.getString(mStringResourceId);
- return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
- }
- }
- }
-
- @Override
- public boolean isConfiguredInMultipleMode() {
- return mIsMultiple;
- }
-
- @Override
- public boolean isTemporary(@UserIdInt int userId) {
- synchronized (mLock) {
- return mTemporaryServiceNamesList.get(userId) != null;
- }
}
@Override
- public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
- int durationMs) {
- setTemporaryServices(userId, new String[]{componentName}, durationMs);
- }
-
- @Override
- public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
- synchronized (mLock) {
- mTemporaryServiceNamesList.put(userId, componentNames);
-
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- synchronized (mLock) {
- resetTemporaryService(userId);
- }
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- };
- } else {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- }
- mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
- mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
- for (int i = 0; i < componentNames.length; i++) {
- notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
- /* isTemporary= */ true);
- }
- }
- }
-
- @Override
- public void resetTemporaryService(@UserIdInt int userId) {
- synchronized (mLock) {
- Slog.i(TAG, "resetting temporary service for user " + userId + " from "
- + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
- mTemporaryServiceNamesList.remove(userId);
- if (mTemporaryHandler != null) {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- mTemporaryHandler = null;
- }
- notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
- /* isTemporary= */ false);
- }
- }
-
- @Override
- public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
- synchronized (mLock) {
- final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
- if (currentlyEnabled == enabled) {
- Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
- return false;
- }
- if (enabled) {
- Slog.i(TAG, "disabling default service for user " + userId);
- mDefaultServicesDisabled.removeAt(userId);
- } else {
- Slog.i(TAG, "enabling default service for user " + userId);
- mDefaultServicesDisabled.put(userId, true);
- }
- }
- return true;
+ public String[] readServiceNameList(int userId) {
+ return mContext.getResources().getStringArray(mArrayResourceId);
}
+ @Nullable
@Override
- public boolean isDefaultServiceEnabled(int userId) {
- synchronized (mLock) {
- return isDefaultServiceEnabledLocked(userId);
- }
+ public String readServiceName(int userId) {
+ return mContext.getResources().getString(mStringResourceId);
}
- private boolean isDefaultServiceEnabledLocked(int userId) {
- return !mDefaultServicesDisabled.get(userId);
- }
-
- @Override
- public String toString() {
- synchronized (mLock) {
- return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
- }
- }
// TODO(b/117779333): support proto
@Override
@@ -314,31 +78,4 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR
pw.print(mDefaultServicesDisabled.size());
}
}
-
- // TODO(b/117779333): support proto
- @Override
- public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
- synchronized (mLock) {
- final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- pw.print("tmpName=");
- pw.print(Arrays.toString(temporaryNames));
- final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
- pw.print(" (expires in ");
- TimeUtils.formatDuration(ttl, pw);
- pw.print("), ");
- }
- pw.print("defaultName=");
- pw.print(getDefaultServiceName(userId));
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- pw.println(disabled ? " (disabled)" : " (enabled)");
- }
- }
-
- private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
- @Nullable String newTemporaryName, boolean isTemporary) {
- if (mOnSetCallback != null) {
- mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
- }
- }
}
diff --git a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
index cac7f53aad66..17d75e600c36 100644
--- a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
@@ -19,8 +19,11 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import java.io.PrintWriter;
+import java.util.Set;
/**
* Gets the service name using a property from the {@link android.provider.Settings.Secure}
@@ -28,21 +31,34 @@ import java.io.PrintWriter;
*
* @hide
*/
-public final class SecureSettingsServiceNameResolver implements ServiceNameResolver {
+public final class SecureSettingsServiceNameResolver extends ServiceNameBaseResolver {
+ /**
+ * The delimiter to be used to parse the secure settings string. Services must make sure
+ * that this delimiter is used while adding component names to their secure setting property.
+ */
+ private static final char COMPONENT_NAME_SEPARATOR = ':';
- private final @NonNull Context mContext;
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@NonNull
private final String mProperty;
public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property) {
- mContext = context;
- mProperty = property;
+ this(context, property, /*isMultiple*/false);
}
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- return Settings.Secure.getStringForUser(mContext.getContentResolver(), mProperty, userId);
+ /**
+ *
+ * @param context the context required to retrieve the secure setting value
+ * @param property name of the secure setting key
+ * @param isMultiple true if the system service using this resolver needs to connect to
+ * multiple remote services, false otherwise
+ */
+ public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property,
+ boolean isMultiple) {
+ super(context, isMultiple);
+ mProperty = property;
}
// TODO(b/117779333): support proto
@@ -61,4 +77,34 @@ public final class SecureSettingsServiceNameResolver implements ServiceNameResol
public String toString() {
return "SecureSettingsServiceNameResolver[" + mProperty + "]";
}
+
+ @Override
+ public String[] readServiceNameList(int userId) {
+ return parseColonDelimitedServiceNames(
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId));
+ }
+
+ @Override
+ public String readServiceName(int userId) {
+ return Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId);
+ }
+
+ private String[] parseColonDelimitedServiceNames(String serviceNames) {
+ final Set<String> delimitedServices = new ArraySet<>();
+ if (!TextUtils.isEmpty(serviceNames)) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(serviceNames);
+ while (splitter.hasNext()) {
+ final String str = splitter.next();
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ delimitedServices.add(str);
+ }
+ }
+ String[] delimitedServicesArray = new String[delimitedServices.size()];
+ return delimitedServices.toArray(delimitedServicesArray);
+ }
}
diff --git a/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
new file mode 100644
index 000000000000..76ea05e36141
--- /dev/null
+++ b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Gets the service name using a framework resources, temporarily changing the service if necessary
+ * (typically during CTS tests or service development).
+ *
+ * @hide
+ */
+public abstract class ServiceNameBaseResolver implements ServiceNameResolver {
+
+ private static final String TAG = ServiceNameBaseResolver.class.getSimpleName();
+
+ /** Handler message to {@link #resetTemporaryService(int)} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
+ @NonNull
+ protected final Context mContext;
+ @NonNull
+ protected final Object mLock = new Object();
+
+ protected final boolean mIsMultiple;
+ /**
+ * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
+ * keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
+ * mIsMultiple is true.
+ */
+ @GuardedBy("mLock")
+ protected final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
+ /**
+ * Map of default services that have been disabled by
+ * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests.
+ */
+ @GuardedBy("mLock")
+ protected final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
+ @Nullable
+ private NameResolverListener mOnSetCallback;
+ /**
+ * When the temporary service will expire (and reset back to the default).
+ */
+ @GuardedBy("mLock")
+ private long mTemporaryServiceExpiration;
+
+ /**
+ * Handler used to reset the temporary service name.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
+ protected ServiceNameBaseResolver(Context context, boolean isMultiple) {
+ mContext = context;
+ mIsMultiple = isMultiple;
+ }
+
+ @Override
+ public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
+ synchronized (mLock) {
+ this.mOnSetCallback = callback;
+ }
+ }
+
+ @Override
+ public String getServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ @Override
+ public String getDefaultServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getDefaultServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getServiceNameList(int userId) {
+ synchronized (mLock) {
+ String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): using temporary name "
+ + Arrays.toString(temporaryNames) + " for user " + userId);
+ return temporaryNames;
+ }
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ if (disabled) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
+ + "user " + userId);
+ return null;
+ }
+ return getDefaultServiceNameList(userId);
+
+ }
+ }
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String[] readServiceNameList(int userId);
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String readServiceName(int userId);
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getDefaultServiceNameList(int userId) {
+ synchronized (mLock) {
+ if (mIsMultiple) {
+ String[] serviceNameList = readServiceNameList(userId);
+ // Filter out unimplemented services
+ // Initialize the validated array as null because we do not know the final size.
+ List<String> validatedServiceNameList = new ArrayList<>();
+ try {
+ for (int i = 0; i < serviceNameList.length; i++) {
+ if (TextUtils.isEmpty(serviceNameList[i])) {
+ continue;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceNameList[i]);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ if (serviceInfo != null) {
+ validatedServiceNameList.add(serviceNameList[i]);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not validate provided services.", e);
+ }
+ String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
+ return validatedServiceNameList.toArray(validatedServiceNameArray);
+ } else {
+ final String name = readServiceName(userId);
+ return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
+ }
+ }
+ }
+
+ @Override
+ public boolean isConfiguredInMultipleMode() {
+ return mIsMultiple;
+ }
+
+ @Override
+ public boolean isTemporary(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mTemporaryServiceNamesList.get(userId) != null;
+ }
+ }
+
+ @Override
+ public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
+ int durationMs) {
+ setTemporaryServices(userId, new String[]{componentName}, durationMs);
+ }
+
+ @Override
+ public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
+ synchronized (mLock) {
+ mTemporaryServiceNamesList.put(userId, componentNames);
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryService(userId);
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+ mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ for (int i = 0; i < componentNames.length; i++) {
+ notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
+ /* isTemporary= */ true);
+ }
+ }
+ }
+
+ @Override
+ public void resetTemporaryService(@UserIdInt int userId) {
+ synchronized (mLock) {
+ Slog.i(TAG, "resetting temporary service for user " + userId + " from "
+ + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
+ mTemporaryServiceNamesList.remove(userId);
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+ notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+ /* isTemporary= */ false);
+ }
+ }
+
+ @Override
+ public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
+ synchronized (mLock) {
+ final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
+ if (currentlyEnabled == enabled) {
+ Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
+ return false;
+ }
+ if (enabled) {
+ Slog.i(TAG, "disabling default service for user " + userId);
+ mDefaultServicesDisabled.removeAt(userId);
+ } else {
+ Slog.i(TAG, "enabling default service for user " + userId);
+ mDefaultServicesDisabled.put(userId, true);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isDefaultServiceEnabled(int userId) {
+ synchronized (mLock) {
+ return isDefaultServiceEnabledLocked(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isDefaultServiceEnabledLocked(int userId) {
+ return !mDefaultServicesDisabled.get(userId);
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
+ }
+ }
+
+ // TODO(b/117779333): support proto
+ @Override
+ public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
+ synchronized (mLock) {
+ final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ pw.print("tmpName=");
+ pw.print(Arrays.toString(temporaryNames));
+ final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
+ pw.print(" (expires in ");
+ TimeUtils.formatDuration(ttl, pw);
+ pw.print("), ");
+ }
+ pw.print("defaultName=");
+ pw.print(getDefaultServiceName(userId));
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ pw.println(disabled ? " (disabled)" : " (enabled)");
+ }
+ }
+
+ private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
+ @Nullable String newTemporaryName, boolean isTemporary) {
+ if (mOnSetCallback != null) {
+ mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 324eefc809e8..36199debaa6e 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -44,6 +44,8 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing the battery state
@@ -63,6 +65,8 @@ final class BatteryController {
@VisibleForTesting
static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds
+ @VisibleForTesting
+ static final long USI_BATTERY_VALIDITY_DURATION_MILLIS = 60 * 60_000; // 1 hour
private final Object mLock = new Object();
private final Context mContext;
@@ -98,8 +102,12 @@ final class BatteryController {
}
public void systemRunning() {
- Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .registerInputDeviceListener(mInputDeviceListener, mHandler);
+ final InputManager inputManager =
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ inputManager.registerInputDeviceListener(mInputDeviceListener, mHandler);
+ for (int deviceId : inputManager.getInputDeviceIds()) {
+ mInputDeviceListener.onInputDeviceAdded(deviceId);
+ }
}
/**
@@ -165,19 +173,20 @@ final class BatteryController {
}
}
- @GuardedBy("mLock")
- private void notifyAllListenersForDeviceLocked(State state) {
- if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state);
- mListenerRecords.forEach((pid, listenerRecord) -> {
- if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) {
- notifyBatteryListener(listenerRecord, state);
- }
- });
+ private void notifyAllListenersForDevice(State state) {
+ synchronized (mLock) {
+ if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state);
+ mListenerRecords.forEach((pid, listenerRecord) -> {
+ if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) {
+ notifyBatteryListener(listenerRecord, state);
+ }
+ });
+ }
}
@GuardedBy("mLock")
private void updatePollingLocked(boolean delayStart) {
- if (mDeviceMonitors.isEmpty() || !mIsInteractive) {
+ if (!mIsInteractive || !anyOf(mDeviceMonitors, DeviceMonitor::requiresPolling)) {
// Stop polling.
mIsPolling = false;
mHandler.removeCallbacks(this::handlePollEvent);
@@ -192,6 +201,13 @@ final class BatteryController {
mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
}
+ private String getInputDeviceName(int deviceId) {
+ final InputDevice device =
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .getInputDevice(deviceId);
+ return device != null ? device.getName() : "<none>";
+ }
+
private boolean hasBattery(int deviceId) {
final InputDevice device =
Objects.requireNonNull(mContext.getSystemService(InputManager.class))
@@ -199,6 +215,13 @@ final class BatteryController {
return device != null && device.hasBattery();
}
+ private boolean isUsiDevice(int deviceId) {
+ final InputDevice device =
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .getInputDevice(deviceId);
+ return device != null && device.supportsUsi();
+ }
+
@GuardedBy("mLock")
private DeviceMonitor getDeviceMonitorOrThrowLocked(int deviceId) {
return Objects.requireNonNull(mDeviceMonitors.get(deviceId),
@@ -252,8 +275,10 @@ final class BatteryController {
if (!hasRegisteredListenerForDeviceLocked(deviceId)) {
// There are no more listeners monitoring this device.
final DeviceMonitor monitor = getDeviceMonitorOrThrowLocked(deviceId);
- monitor.stopMonitoring();
- mDeviceMonitors.remove(deviceId);
+ if (!monitor.isPersistent()) {
+ monitor.onMonitorDestroy();
+ mDeviceMonitors.remove(deviceId);
+ }
}
if (listenerRecord.mMonitoredDevices.isEmpty()) {
@@ -298,9 +323,7 @@ final class BatteryController {
if (monitor == null) {
return;
}
- if (monitor.updateBatteryState(eventTime)) {
- notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
- }
+ monitor.onUEvent(eventTime);
}
}
@@ -310,18 +333,22 @@ final class BatteryController {
return;
}
final long eventTime = SystemClock.uptimeMillis();
- mDeviceMonitors.forEach((deviceId, monitor) -> {
- // Re-acquire lock in the lambda to silence error-prone build warnings.
- synchronized (mLock) {
- if (monitor.updateBatteryState(eventTime)) {
- notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
- }
- }
- });
+ mDeviceMonitors.forEach((deviceId, monitor) -> monitor.onPoll(eventTime));
mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS);
}
}
+ private void handleMonitorTimeout(int deviceId) {
+ synchronized (mLock) {
+ final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+ if (monitor == null) {
+ return;
+ }
+ final long updateTime = SystemClock.uptimeMillis();
+ monitor.onTimeout(updateTime);
+ }
+ }
+
/** Gets the current battery state of an input device. */
public IInputDeviceBatteryState getBatteryState(int deviceId) {
synchronized (mLock) {
@@ -329,15 +356,11 @@ final class BatteryController {
final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
if (monitor == null) {
// The input device's battery is not being monitored by any listener.
- return queryBatteryStateFromNative(deviceId, updateTime);
+ return queryBatteryStateFromNative(deviceId, updateTime, hasBattery(deviceId));
}
// Force the battery state to update, and notify listeners if necessary.
- final boolean stateChanged = monitor.updateBatteryState(updateTime);
- final State state = monitor.getBatteryStateForReporting();
- if (stateChanged) {
- notifyAllListenersForDeviceLocked(state);
- }
- return state;
+ monitor.onPoll(updateTime);
+ return monitor.getBatteryStateForReporting();
}
}
@@ -379,7 +402,14 @@ final class BatteryController {
private final InputManager.InputDeviceListener mInputDeviceListener =
new InputManager.InputDeviceListener() {
@Override
- public void onInputDeviceAdded(int deviceId) {}
+ public void onInputDeviceAdded(int deviceId) {
+ synchronized (mLock) {
+ if (isUsiDevice(deviceId) && !mDeviceMonitors.containsKey(deviceId)) {
+ // Start monitoring USI device immediately.
+ mDeviceMonitors.put(deviceId, new UsiDeviceMonitor(deviceId));
+ }
+ }
+ }
@Override
public void onInputDeviceRemoved(int deviceId) {}
@@ -392,9 +422,7 @@ final class BatteryController {
return;
}
final long eventTime = SystemClock.uptimeMillis();
- if (monitor.updateBatteryState(eventTime)) {
- notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
- }
+ monitor.onConfiguration(eventTime);
}
}
};
@@ -422,8 +450,7 @@ final class BatteryController {
}
// Queries the battery state of an input device from native code.
- private State queryBatteryStateFromNative(int deviceId, long updateTime) {
- final boolean isPresent = hasBattery(deviceId);
+ private State queryBatteryStateFromNative(int deviceId, long updateTime, boolean isPresent) {
return new State(
deviceId,
updateTime,
@@ -434,8 +461,9 @@ final class BatteryController {
// Holds the state of an InputDevice for which battery changes are currently being monitored.
private class DeviceMonitor {
- @NonNull
- private State mState;
+ protected final State mState;
+ // Represents whether the input device has a sysfs battery node.
+ protected boolean mHasBattery = false;
@Nullable
private UEventBatteryListener mUEventBatteryListener;
@@ -445,26 +473,32 @@ final class BatteryController {
// Load the initial battery state and start monitoring.
final long eventTime = SystemClock.uptimeMillis();
- updateBatteryState(eventTime);
+ configureDeviceMonitor(eventTime);
}
- // Returns true if the battery state changed since the last time it was updated.
- public boolean updateBatteryState(long updateTime) {
- mState.updateTime = updateTime;
-
- final State updatedState = queryBatteryStateFromNative(mState.deviceId, updateTime);
- if (mState.equals(updatedState)) {
- return false;
+ protected void processChangesAndNotify(long eventTime, Consumer<Long> changes) {
+ final State oldState = getBatteryStateForReporting();
+ changes.accept(eventTime);
+ final State newState = getBatteryStateForReporting();
+ if (!oldState.equals(newState)) {
+ notifyAllListenersForDevice(newState);
}
- if (mState.isPresent != updatedState.isPresent) {
- if (updatedState.isPresent) {
+ }
+
+ public void onConfiguration(long eventTime) {
+ processChangesAndNotify(eventTime, this::configureDeviceMonitor);
+ }
+
+ private void configureDeviceMonitor(long eventTime) {
+ if (mHasBattery != hasBattery(mState.deviceId)) {
+ mHasBattery = !mHasBattery;
+ if (mHasBattery) {
startMonitoring();
} else {
stopMonitoring();
}
+ updateBatteryStateFromNative(eventTime);
}
- mState = updatedState;
- return true;
}
private void startMonitoring() {
@@ -483,19 +517,46 @@ final class BatteryController {
mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath));
}
- private String formatDevPath(String path) {
+ private String formatDevPath(@NonNull String path) {
// Remove the "/sys" prefix if it has one.
return path.startsWith("/sys") ? path.substring(4) : path;
}
- // This must be called when the device is no longer being monitored.
- public void stopMonitoring() {
+ private void stopMonitoring() {
if (mUEventBatteryListener != null) {
mUEventManager.removeListener(mUEventBatteryListener);
mUEventBatteryListener = null;
}
}
+ // This must be called when the device is no longer being monitored.
+ public void onMonitorDestroy() {
+ stopMonitoring();
+ }
+
+ protected void updateBatteryStateFromNative(long eventTime) {
+ mState.updateIfChanged(
+ queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery));
+ }
+
+ public void onPoll(long eventTime) {
+ processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
+ }
+
+ public void onUEvent(long eventTime) {
+ processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
+ }
+
+ public boolean requiresPolling() {
+ return true;
+ }
+
+ public boolean isPersistent() {
+ return false;
+ }
+
+ public void onTimeout(long eventTime) {}
+
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
return new State(mState);
@@ -503,8 +564,98 @@ final class BatteryController {
@Override
public String toString() {
- return "state=" + mState
- + ", uEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
+ return "DeviceId=" + mState.deviceId
+ + ", Name='" + getInputDeviceName(mState.deviceId) + "'"
+ + ", NativeBattery=" + mState
+ + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
+ }
+ }
+
+ // Battery monitoring logic that is specific to stylus devices that support the
+ // Universal Stylus Initiative (USI) protocol.
+ private class UsiDeviceMonitor extends DeviceMonitor {
+
+ // For USI devices, we only treat the battery state as valid for a fixed amount of time
+ // after receiving a battery update. Once the timeout has passed, we signal to all listeners
+ // that there is no longer a battery present for the device. The battery state is valid
+ // as long as this callback is non-null.
+ @Nullable
+ private Runnable mValidityTimeoutCallback;
+
+ UsiDeviceMonitor(int deviceId) {
+ super(deviceId);
+ }
+
+ @Override
+ public void onPoll(long eventTime) {
+ // Disregard polling for USI devices.
+ }
+
+ @Override
+ public void onUEvent(long eventTime) {
+ processChangesAndNotify(eventTime, (time) -> {
+ updateBatteryStateFromNative(time);
+ markUsiBatteryValid();
+ });
+ }
+
+ @Override
+ public void onTimeout(long eventTime) {
+ processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid());
+ }
+
+ @Override
+ public void onConfiguration(long eventTime) {
+ super.onConfiguration(eventTime);
+
+ if (!mHasBattery) {
+ throw new IllegalStateException(
+ "UsiDeviceMonitor: USI devices are always expected to "
+ + "report a valid battery, but no battery was detected!");
+ }
+ }
+
+ private void markUsiBatteryValid() {
+ if (mValidityTimeoutCallback != null) {
+ mHandler.removeCallbacks(mValidityTimeoutCallback);
+ } else {
+ final int deviceId = mState.deviceId;
+ mValidityTimeoutCallback =
+ () -> BatteryController.this.handleMonitorTimeout(deviceId);
+ }
+ mHandler.postDelayed(mValidityTimeoutCallback, USI_BATTERY_VALIDITY_DURATION_MILLIS);
+ }
+
+ private void markUsiBatteryInvalid() {
+ if (mValidityTimeoutCallback == null) {
+ return;
+ }
+ mHandler.removeCallbacks(mValidityTimeoutCallback);
+ mValidityTimeoutCallback = null;
+ }
+
+ @Override
+ public State getBatteryStateForReporting() {
+ return mValidityTimeoutCallback != null
+ ? new State(mState) : new State(mState.deviceId);
+ }
+
+ @Override
+ public boolean requiresPolling() {
+ // Do not poll the battery state for USI devices.
+ return false;
+ }
+
+ @Override
+ public boolean isPersistent() {
+ // Do not remove the battery monitor for USI devices.
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + ", UsiStateIsValid=" + (mValidityTimeoutCallback != null);
}
}
@@ -548,18 +699,33 @@ final class BatteryController {
private static class State extends IInputDeviceBatteryState {
State(int deviceId) {
- initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN,
- Float.NaN /*capacity*/);
+ reset(deviceId);
}
State(IInputDeviceBatteryState s) {
- initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity);
+ copyFrom(s);
}
State(int deviceId, long updateTime, boolean isPresent, int status, float capacity) {
initialize(deviceId, updateTime, isPresent, status, capacity);
}
+ // Updates this from other if there is a difference between them, ignoring the updateTime.
+ public void updateIfChanged(IInputDeviceBatteryState other) {
+ if (!equalsIgnoringUpdateTime(other)) {
+ copyFrom(other);
+ }
+ }
+
+ public void reset(int deviceId) {
+ initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN,
+ Float.NaN /*capacity*/);
+ }
+
+ private void copyFrom(IInputDeviceBatteryState s) {
+ initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity);
+ }
+
private void initialize(int deviceId, long updateTime, boolean isPresent, int status,
float capacity) {
this.deviceId = deviceId;
@@ -569,11 +735,34 @@ final class BatteryController {
this.capacity = capacity;
}
+ private boolean equalsIgnoringUpdateTime(IInputDeviceBatteryState other) {
+ long updateTime = this.updateTime;
+ this.updateTime = other.updateTime;
+ boolean eq = this.equals(other);
+ this.updateTime = updateTime;
+ return eq;
+ }
+
@Override
public String toString() {
- return "BatteryState{deviceId=" + deviceId + ", updateTime=" + updateTime
- + ", isPresent=" + isPresent + ", status=" + status + ", capacity=" + capacity
- + " }";
+ if (!isPresent) {
+ return "State{<not present>}";
+ }
+ return "State{time=" + updateTime
+ + ", isPresent=" + isPresent
+ + ", status=" + status
+ + ", capacity=" + capacity
+ + "}";
}
}
+
+ // Check if any value in an ArrayMap matches the predicate in an optimized way.
+ private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
+ for (int i = 0; i < arrayMap.size(); i++) {
+ if (test.test(arrayMap.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index a4e295b4f7df..bf00a33d7d20 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -203,6 +203,12 @@ public interface Computer extends PackageDataSnapshot {
boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, int userId,
long flags);
boolean isCallerSameApp(String packageName, int uid);
+ /**
+ * Returns true if the package name and the uid represent the same app.
+ *
+ * @param resolveIsolatedUid if true, resolves an isolated uid into the real uid.
+ */
+ boolean isCallerSameApp(String packageName, int uid, boolean resolveIsolatedUid);
boolean isComponentVisibleToInstantApp(@Nullable ComponentName component);
boolean isComponentVisibleToInstantApp(@Nullable ComponentName component,
@PackageManager.ComponentType int type);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5d479d52d6cc..86b8272dbe00 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2209,11 +2209,19 @@ public class ComputerEngine implements Computer {
}
public final boolean isCallerSameApp(String packageName, int uid) {
+ return isCallerSameApp(packageName, uid, false /* resolveIsolatedUid */);
+ }
+
+ @Override
+ public final boolean isCallerSameApp(String packageName, int uid, boolean resolveIsolatedUid) {
if (Process.isSdkSandboxUid(uid)) {
return (packageName != null
&& packageName.equals(mService.getSdkSandboxPackageName()));
}
AndroidPackage pkg = mPackages.get(packageName);
+ if (resolveIsolatedUid && Process.isIsolated(uid)) {
+ uid = getIsolatedOwner(uid);
+ }
return pkg != null
&& UserHandle.getAppId(uid) == pkg.getUid();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8fed153825db..6e54d0bbd656 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5242,25 +5242,30 @@ public class PackageManagerService implements PackageSender, TestUtilityService
Map<String, String> classLoaderContextMap,
String loaderIsa) {
int callingUid = Binder.getCallingUid();
- if (PackageManagerService.PLATFORM_PACKAGE_NAME.equals(loadingPackageName)
- && callingUid != Process.SYSTEM_UID) {
+
+ // TODO(b/254043366): System server should not report its own dex load because there's
+ // nothing ART can do with it.
+
+ Computer snapshot = snapshot();
+
+ // System server should be able to report dex load on behalf of other apps. E.g., it
+ // could potentially resend the notifications in order to migrate the existing dex load
+ // info to ART Service.
+ if (!PackageManagerServiceUtils.isSystemOrRoot()
+ && !snapshot.isCallerSameApp(
+ loadingPackageName, callingUid, true /* resolveIsolatedUid */)) {
Slog.w(PackageManagerService.TAG,
- "Non System Server process reporting dex loads as system server. uid="
- + callingUid);
- // Do not record dex loads from processes pretending to be system server.
- // Only the system server should be assigned the package "android", so reject calls
- // that don't satisfy the constraint.
- //
- // notifyDexLoad is a PM API callable from the app process. So in theory, apps could
- // craft calls to this API and pretend to be system server. Doing so poses no
- // particular danger for dex load reporting or later dexopt, however it is a
- // sensible check to do in order to verify the expectations.
+ TextUtils.formatSimple(
+ "Invalid dex load report. loadingPackageName=%s, uid=%d",
+ loadingPackageName, callingUid));
return;
}
+ // TODO(b/254043366): Call `ArtManagerLocal.notifyDexLoad`.
+
int userId = UserHandle.getCallingUserId();
- ApplicationInfo ai = snapshot().getApplicationInfo(loadingPackageName, /*flags*/ 0,
- userId);
+ ApplicationInfo ai =
+ snapshot.getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
if (ai == null) {
Slog.w(PackageManagerService.TAG, "Loading a package that does not exist for the calling user. package="
+ loadingPackageName + ", user=" + userId);
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 47a3705388b6..415ddd396ad9 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -707,7 +707,7 @@ final class VerifyingSession {
private List<ComponentName> matchVerifiers(PackageInfoLite pkgInfo,
List<ResolveInfo> receivers, final PackageVerificationState verificationState) {
- if (pkgInfo.verifiers.length == 0) {
+ if (pkgInfo.verifiers == null || pkgInfo.verifiers.length == 0) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index c81a3eeab965..799ef41f3067 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -649,8 +649,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
Permission bp = mRegistry.getPermission(info.name);
added = bp == null;
int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+ enforcePermissionCapLocked(info, tree);
if (added) {
- enforcePermissionCapLocked(info, tree);
bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC);
} else if (!bp.isDynamic()) {
throw new SecurityException("Not allowed to modify non-dynamic permission "
@@ -2156,6 +2156,46 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
/**
+ * If the package was below api 23, got the SYSTEM_ALERT_WINDOW permission automatically, and
+ * then updated past api 23, and the app does not satisfy any of the other SAW permission flags,
+ * the permission should be revoked.
+ *
+ * @param newPackage The new package that was installed
+ * @param oldPackage The old package that was updated
+ */
+ private void revokeSystemAlertWindowIfUpgradedPast23(
+ @NonNull AndroidPackage newPackage,
+ @NonNull AndroidPackage oldPackage) {
+ if (oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.M
+ || newPackage.getTargetSdkVersion() < Build.VERSION_CODES.M
+ || !newPackage.getRequestedPermissions()
+ .contains(Manifest.permission.SYSTEM_ALERT_WINDOW)) {
+ return;
+ }
+
+ Permission saw;
+ synchronized (mLock) {
+ saw = mRegistry.getPermission(Manifest.permission.SYSTEM_ALERT_WINDOW);
+ }
+ final PackageStateInternal ps =
+ mPackageManagerInt.getPackageStateInternal(newPackage.getPackageName());
+ if (shouldGrantPermissionByProtectionFlags(newPackage, ps, saw, new ArraySet<>())
+ || shouldGrantPermissionBySignature(newPackage, saw)) {
+ return;
+ }
+ for (int userId : getAllUserIds()) {
+ try {
+ revokePermissionFromPackageForUser(newPackage.getPackageName(),
+ Manifest.permission.SYSTEM_ALERT_WINDOW, false, userId,
+ mDefaultPermissionCallback);
+ } catch (IllegalStateException | SecurityException e) {
+ Log.e(TAG, "unable to revoke SYSTEM_ALERT_WINDOW for "
+ + newPackage.getPackageName() + " user " + userId, e);
+ }
+ }
+ }
+
+ /**
* We might auto-grant permissions if any permission of the group is already granted. Hence if
* the group of a granted permission changes we need to revoke it to avoid having permissions of
* the new group auto-granted.
@@ -4691,6 +4731,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
if (hasOldPkg) {
revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg);
revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg);
+ revokeSystemAlertWindowIfUpgradedPast23(pkg, oldPkg);
}
if (hasPermissionDefinitionChanges) {
revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ae998067fa03..a6fac4d60fe7 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -114,6 +114,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Rect;
+import android.hardware.SensorPrivacyManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.hdmi.HdmiAudioSystemClient;
@@ -391,6 +392,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
IStatusBarService mStatusBarService;
StatusBarManagerInternal mStatusBarManagerInternal;
AudioManagerInternal mAudioManagerInternal;
+ SensorPrivacyManager mSensorPrivacyManager;
DisplayManager mDisplayManager;
DisplayManagerInternal mDisplayManagerInternal;
boolean mPreloadedRecentApps;
@@ -1912,6 +1914,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mPackageManager = mContext.getPackageManager();
@@ -2999,8 +3002,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if ((metaState & KeyEvent.META_META_MASK) == 0) {
return key_not_consumed;
}
- // Share the same behavior with KEYCODE_LANGUAGE_SWITCH.
- case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
if (down && repeatCount == 0) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
@@ -3081,6 +3082,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return key_not_consumed;
}
+ private void toggleMicrophoneMuteFromKey() {
+ if (mSensorPrivacyManager.supportsSensorToggle(
+ SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
+ SensorPrivacyManager.Sensors.MICROPHONE)) {
+ boolean isEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
+ SensorPrivacyManager.Sensors.MICROPHONE);
+ mSensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sensors.MICROPHONE,
+ !isEnabled);
+ }
+ }
+
/**
* TV only: recognizes a remote control gesture for capturing a bug report.
*/
@@ -4013,11 +4026,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
}
+ case KeyEvent.KEYCODE_MUTE:
+ result &= ~ACTION_PASS_TO_USER;
+ if (down && event.getRepeatCount() == 0) {
+ toggleMicrophoneMuteFromKey();
+ }
+ break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
@@ -4195,7 +4213,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mRequestedOrSleepingDefaultDisplay) {
mCameraGestureTriggeredDuringGoingToSleep = true;
// Wake device up early to prevent display doing redundant turning off/on stuff.
- wakeUpFromPowerKey(event.getDownTime());
+ wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
+ PowerManager.WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
}
return true;
}
@@ -4728,11 +4748,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
mDefaultDisplayRotation.updateOrientationListener();
reportScreenStateToVrManager(false);
- if (mCameraGestureTriggeredDuringGoingToSleep) {
- wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
- PowerManager.WAKE_REASON_CAMERA_LAUNCH,
- "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
- }
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4784723b7735..d8b1120c624d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -42,8 +42,6 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.SynchronousUserSwitchObserver;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -64,7 +62,6 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatterySaverPolicyConfig;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -127,7 +124,6 @@ import com.android.server.UiThread;
import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
-import com.android.server.compat.PlatformCompat;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.policy.WindowManagerPolicy;
@@ -284,17 +280,6 @@ public final class PowerManagerService extends SystemService
*/
private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
- /**
- * Apps targeting Android U and above need to define
- * {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for
- * {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect.
- * Note that most applications should use {@link android.R.attr#turnScreenOn} or
- * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the
- * previous foreground app from being resumed first when the screen turns on.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L;
/** Reason ID for holding display suspend blocker. */
private static final String HOLDING_DISPLAY_SUSPEND_BLOCKER = "holding display";
@@ -318,7 +303,6 @@ public final class PowerManagerService extends SystemService
private final SystemPropertiesWrapper mSystemProperties;
private final Clock mClock;
private final Injector mInjector;
- private final PlatformCompat mPlatformCompat;
private AppOpsManager mAppOpsManager;
private LightsManager mLightsManager;
@@ -1012,11 +996,6 @@ public final class PowerManagerService extends SystemService
public void set(String key, String val) {
SystemProperties.set(key, val);
}
-
- @Override
- public boolean getBoolean(String key, boolean def) {
- return SystemProperties.getBoolean(key, def);
- }
};
}
@@ -1053,10 +1032,6 @@ public final class PowerManagerService extends SystemService
AppOpsManager createAppOpsManager(Context context) {
return context.getSystemService(AppOpsManager.class);
}
-
- PlatformCompat createPlatformCompat(Context context) {
- return context.getSystemService(PlatformCompat.class);
- }
}
final Constants mConstants;
@@ -1114,8 +1089,6 @@ public final class PowerManagerService extends SystemService
mAppOpsManager = injector.createAppOpsManager(mContext);
- mPlatformCompat = injector.createPlatformCompat(mContext);
-
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
// Save brightness values:
@@ -1626,28 +1599,14 @@ public final class PowerManagerService extends SystemService
}
if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName)
== AppOpsManager.MODE_ALLOWED) {
- if (mPlatformCompat.isChangeEnabledByPackageName(REQUIRE_TURN_SCREEN_ON_PERMISSION,
- opPackageName, UserHandle.getUserId(opUid))) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)
- == PackageManager.PERMISSION_GRANTED) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Allowing device wake-up from app " + opPackageName);
- }
- return true;
- }
- } else {
- // android.permission.TURN_SCREEN_ON has only been introduced in Android U, only
- // check for appOp for apps targeting lower SDK versions
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Allowing device wake-up from app with "
- + "REQUIRE_TURN_SCREEN_ON_PERMISSION disabled " + opPackageName);
- }
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TURN_SCREEN_ON)
+ == PackageManager.PERMISSION_GRANTED) {
+ Slog.i(TAG, "Allowing device wake-up from app " + opPackageName);
return true;
}
}
- if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
- Slog.d(TAG, "Device wake-up will be denied without android.permission.TURN_SCREEN_ON");
+ if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
+ Slog.d(TAG, "Device wake-up allowed by debug.power.permissionless_turn_screen_on");
return true;
}
Slog.w(TAG, "Not allowing device wake-up for " + opPackageName);
diff --git a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
index c68f9c63b13b..1acf798eb099 100644
--- a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
+++ b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
@@ -48,19 +48,4 @@ interface SystemPropertiesWrapper {
* SELinux. libc will log the underlying reason.
*/
void set(@NonNull String key, @Nullable String val);
-
- /**
- * Get the value for the given {@code key}, returned as a boolean.
- * Values 'n', 'no', '0', 'false' or 'off' are considered false.
- * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
- * (case sensitive).
- * If the key does not exist, or has any other value, then the default
- * result is returned.
- *
- * @param key the key to lookup
- * @param def a default value to return
- * @return the key parsed as a boolean, or def if the key isn't found or is
- * not able to be parsed as a boolean.
- */
- boolean getBoolean(@NonNull String key, boolean def);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7ccf85f34737..d378b11f02bf 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2272,6 +2272,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ boolean proto = false;
+ for (int i = 0; i < args.length; i++) {
+ if ("--proto".equals(args[i])) {
+ proto = true;
+ }
+ }
+ if (proto) {
+ if (mBar == null) return;
+ try (TransferPipe tp = new TransferPipe()) {
+ // Sending the command to the remote, which needs to execute async to avoid blocking
+ // See Binder#dumpAsync() for inspiration
+ mBar.dumpProto(args, tp.getWriteFd());
+ // Times out after 5s
+ tp.go(fd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Error sending command to IStatusBar", t);
+ }
+ return;
+ }
synchronized (mLock) {
for (int i = 0; i < mDisplayUiState.size(); i++) {
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index ff0529f35057..8a6f92750501 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -16,10 +16,13 @@
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
+
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import android.annotation.NonNull;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
import com.android.i18n.timezone.ZoneInfoDb;
@@ -53,7 +56,12 @@ public class ZoneInfoDbTimeZoneProviderEventPreProcessor
// enables immediate failover to a secondary provider, one that might provide valid IDs for
// the same location, which should provide better behavior than just ignoring the event.
if (hasInvalidZones(event)) {
- return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis());
+ TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
+ event.getTimeZoneProviderStatus())
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
+ return TimeZoneProviderEvent.createUncertainEvent(
+ event.getCreationElapsedMillis(), providerStatus);
}
return event;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2232aa1be76f..81bb3a1f40bc 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8454,7 +8454,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
mCompatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
- resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
+ resolvedConfig.screenLayout = computeScreenLayout(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
resolvedConfig.screenHeightDp);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3c847ce0ed89..739f41f170aa 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2188,8 +2188,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
- computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, dw, dh,
- mDisplayMetrics.density, outConfig);
+ computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
mDisplayInfo);
@@ -2289,8 +2288,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
displayInfo.appHeight = appBounds.height();
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
- computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh,
- mDisplayMetrics.density, outConfig);
+ computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
return displayInfo;
}
@@ -2309,6 +2307,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
outConfig.screenHeightDp = (int) (info.mConfigFrame.height() / density + 0.5f);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
+ outConfig.screenLayout = computeScreenLayout(
+ Configuration.resetScreenLayout(outConfig.screenLayout),
+ outConfig.screenWidthDp, outConfig.screenHeightDp);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh);
@@ -2450,7 +2451,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return curSize;
}
- private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated,
+ private void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
int dw, int dh, float density, Configuration outConfig) {
// We need to determine the smallest width that will occur under normal
@@ -2477,31 +2478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (outConfig == null) {
return;
}
- int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh);
- sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw);
- sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh);
- sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw);
outConfig.smallestScreenWidthDp =
(int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
- outConfig.screenLayout = sl;
- }
-
- private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) {
- // Get the app screen size at this rotation.
- final Rect size = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
-
- // Compute the screen layout size class for this rotation.
- int longSize = size.width();
- int shortSize = size.height();
- if (longSize < shortSize) {
- int tmp = longSize;
- longSize = shortSize;
- shortSize = tmp;
- }
- longSize = (int) (longSize / density + 0.5f);
- shortSize = (int) (shortSize / density + 0.5f);
- return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
}
private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 55055390b0ff..449e77fca399 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -55,6 +55,7 @@ import android.view.animation.Transformation;
import android.window.ScreenCapture;
import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.display.DisplayControl;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -246,7 +247,7 @@ class ScreenRotationAnimation {
HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
- mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer,
+ mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
screenshotBuffer.getColorSpace());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -489,8 +490,8 @@ class ScreenRotationAnimation {
return false;
}
if (!mStarted) {
- mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
- mDisplayContent.getWindowingLayer());
+ mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
+ finalWidth, finalHeight);
startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
exitAnim, enterAnim);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 885968f619f6..391d081ce995 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -22,7 +22,6 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -5787,12 +5786,10 @@ class Task extends TaskFragment {
return false;
}
- // Existing Tasks can be reused if a new root task will be created anyway, or for the
- // Dream - because there can only ever be one DreamActivity.
+ // Existing Tasks can be reused if a new root task will be created anyway.
final int windowingMode = getWindowingMode();
final int activityType = getActivityType();
- return DisplayContent.alwaysCreateRootTask(windowingMode, activityType)
- || activityType == ACTIVITY_TYPE_DREAM;
+ return DisplayContent.alwaysCreateRootTask(windowingMode, activityType);
}
void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index efb630291eb7..230b760ce39f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2189,7 +2189,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
compatScreenHeightDp = inOutConfig.screenHeightDp;
}
// Reducing the screen layout starting from its parent config.
- inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
+ inOutConfig.screenLayout = computeScreenLayout(parentConfig.screenLayout,
compatScreenWidthDp, compatScreenHeightDp);
}
}
@@ -2252,16 +2252,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
}
- /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
- static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
- int screenHeightDp) {
- sourceScreenLayout = sourceScreenLayout
- & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
- final int longSize = Math.max(screenWidthDp, screenHeightDp);
- final int shortSize = Math.min(screenWidthDp, screenHeightDp);
- return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
- }
-
@Override
public int getActivityType() {
final int applicationType = super.getActivityType();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4459d45f60a8..b2c8b7ab98d7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -83,11 +83,11 @@ import android.window.TransitionInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.wm.utils.RotationAnimationUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2190,7 +2190,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
changeInfo.mSnapshot = snapshotSurface;
if (isDisplayRotation) {
// This isn't cheap, so only do it for display rotations.
- changeInfo.mSnapshotLuma = RotationAnimationUtils.getMedianBorderLuma(
+ changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
}
SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9763df6b967a..c4c66d8c9c3c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1607,6 +1607,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
+ static int computeScreenLayout(int sourceScreenLayout, int screenWidthDp,
+ int screenHeightDp) {
+ sourceScreenLayout = sourceScreenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+ final int longSize = Math.max(screenWidthDp, screenHeightDp);
+ final int shortSize = Math.min(screenWidthDp, screenHeightDp);
+ return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
+ }
+
// TODO: Users would have their own window containers under the display container?
void switchUser(int userId) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a5cd8a92003c..c9d3dac104de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8698,11 +8698,12 @@ public class WindowManagerService extends IWindowManager.Stub
h.ownerPid = callingPid;
if (region == null) {
- h.replaceTouchableRegionWithCrop = true;
+ h.replaceTouchableRegionWithCrop(null);
} else {
h.touchableRegion.set(region);
+ h.replaceTouchableRegionWithCrop = false;
+ h.setTouchableRegionCrop(surface);
}
- h.setTouchableRegionCrop(null /* use the input surface's bounds */);
final SurfaceControl.Transaction t = mTransactionFactory.get();
t.setInputWindowInfo(surface, h);
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 805559035ef9..7c481f51dfd0 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -448,14 +448,8 @@ class WindowToken extends WindowContainer<WindowState> {
if (mFixedRotationTransformState != null) {
mFixedRotationTransformState.disassociate(this);
}
- // TODO(b/233855302): Remove TaskFragment override if the DisplayContent uses the same
- // bounds for screenLayout calculation.
- final Configuration overrideConfig = new Configuration(config);
- overrideConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
- overrideConfig.screenLayout, overrideConfig.screenWidthDp,
- overrideConfig.screenHeightDp);
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
- overrideConfig, mDisplayContent.getRotation());
+ new Configuration(config), mDisplayContent.getRotation());
mFixedRotationTransformState.mAssociatedTokens.add(this);
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
onFixedRotationStatePrepared();
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
index b93b8d866a00..c11a6d02eb18 100644
--- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
@@ -16,24 +16,11 @@
package com.android.server.wm.utils;
-import static android.hardware.HardwareBuffer.RGBA_8888;
import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
-import android.graphics.Color;
-import android.graphics.ColorSpace;
import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.media.Image;
-import android.media.ImageReader;
-import android.view.Display;
import android.view.Surface;
-import android.view.SurfaceControl;
-import android.window.ScreenCapture;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
@@ -46,89 +33,6 @@ public class RotationAnimationUtils {
return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
}
- /**
- * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
- * luminance at the borders of the bitmap
- * @return the average luminance of all the pixels at the borders of the bitmap
- */
- public static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
- // Cannot read content from buffer with protected usage.
- if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
- || hasProtectedContent(hardwareBuffer)) {
- return 0;
- }
-
- ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
- hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
- ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
- Image image = ir.acquireLatestImage();
- if (image == null || image.getPlanes().length == 0) {
- return 0;
- }
-
- Image.Plane plane = image.getPlanes()[0];
- ByteBuffer buffer = plane.getBuffer();
- int width = image.getWidth();
- int height = image.getHeight();
- int pixelStride = plane.getPixelStride();
- int rowStride = plane.getRowStride();
- float[] borderLumas = new float[2 * width + 2 * height];
-
- // Grab the top and bottom borders
- int l = 0;
- for (int x = 0; x < width; x++) {
- borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
- }
-
- // Grab the left and right borders
- for (int y = 0; y < height; y++) {
- borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
- }
-
- // Cleanup
- ir.close();
-
- // Oh, is this too simple and inefficient for you?
- // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
- Arrays.sort(borderLumas);
- return borderLumas[borderLumas.length / 2];
- }
-
- private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
- int pixelStride, int rowStride) {
- int offset = y * rowStride + x * pixelStride;
- int pixel = 0;
- pixel |= (buffer.get(offset) & 0xff) << 16; // R
- pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
- pixel |= (buffer.get(offset + 2) & 0xff); // B
- pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
- return Color.valueOf(pixel).luminance();
- }
-
- /**
- * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
- * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
- */
- public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
- if (surfaceControl == null) {
- return 0;
- }
-
- Point size = new Point();
- display.getSize(size);
- Rect crop = new Rect(0, 0, size.x, size.y);
- ScreenCapture.ScreenshotHardwareBuffer buffer =
- ScreenCapture.captureLayers(surfaceControl, crop, 1);
- if (buffer == null) {
- return 0;
- }
-
- return RotationAnimationUtils.getMedianBorderLuma(buffer.getHardwareBuffer(),
- buffer.getColorSpace());
- }
-
public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
switch (rotation) {
case Surface.ROTATION_0:
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 91f5c6999427..352a257e2915 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -49,7 +49,8 @@ public final class CredentialManagerService extends
public CredentialManagerService(@NonNull Context context) {
super(context,
- new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
+ new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE,
+ /*isMultiple=*/true),
null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index f45f62682f1c..aa19241e77dd 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -17,6 +17,11 @@
package com.android.server.credentials;
import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
import android.util.Log;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -24,7 +29,7 @@ import com.android.server.infra.AbstractPerUserSystemService;
/**
* Per-user implementation of {@link CredentialManagerService}
*/
-public class CredentialManagerServiceImpl extends
+public final class CredentialManagerServiceImpl extends
AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
private static final String TAG = "CredManSysServiceImpl";
@@ -34,6 +39,20 @@ public class CredentialManagerServiceImpl extends
super(master, lock, userId);
}
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ return si;
+ }
+
/**
* Unimplemented getCredentials
*/
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9e449aedf0d9..b74fedf3ff46 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1792,7 +1792,8 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartStatusBarManagerService");
try {
statusBar = new StatusBarManagerService(context);
- ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
+ ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+ DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
} catch (Throwable e) {
reportWtf("starting StatusBarManagerService", e);
}
diff --git a/services/tests/mockingservicestests/OWNERS b/services/tests/mockingservicestests/OWNERS
index 2bb16496e0f0..4dda51f2004f 100644
--- a/services/tests/mockingservicestests/OWNERS
+++ b/services/tests/mockingservicestests/OWNERS
@@ -1,5 +1,8 @@
include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+
+# Game Platform
per-file FakeGameClassifier.java = file:/GAME_MANAGER_OWNERS
per-file FakeGameServiceProviderInstance = file:/GAME_MANAGER_OWNERS
per-file FakeServiceConnector.java = file:/GAME_MANAGER_OWNERS
per-file Game* = file:/GAME_MANAGER_OWNERS
+per-file res/xml/game_manager* = file:/GAME_MANAGER_OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index abc32c9339ae..b7e66f23a706 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -291,20 +291,30 @@ public class BroadcastQueueModernImplTest {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+ // enqueue a bg-priority broadcast then a fg-priority one
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
+ queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ // verify that:
+ // (a) the queue is immediately runnable by existence of a fg-priority broadcast
+ // (b) the next one up is the fg-priority broadcast despite its later enqueue time
queue.setProcessCached(false);
assertTrue(queue.isRunnable());
assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
queue.setProcessCached(true);
assertTrue(queue.isRunnable());
assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index c12544897941..d9a26c68f3ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -549,12 +549,6 @@ public class BroadcastQueueTest {
receivers, false, null, null, userId);
}
- private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp,
- List<Object> receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) {
- return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
- receivers, true, orderedResultTo, orderedExtras, UserHandle.USER_SYSTEM);
- }
-
private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
BroadcastOptions options, List<Object> receivers) {
return makeBroadcastRecord(intent, callerApp, options,
@@ -562,12 +556,24 @@ public class BroadcastQueueTest {
}
private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
+ List<Object> receivers, IIntentReceiver resultTo) {
+ return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+ receivers, false, resultTo, null, UserHandle.USER_SYSTEM);
+ }
+
+ private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp,
+ List<Object> receivers, IIntentReceiver resultTo, Bundle resultExtras) {
+ return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
+ receivers, true, resultTo, resultExtras, UserHandle.USER_SYSTEM);
+ }
+
+ private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
BroadcastOptions options, List<Object> receivers, boolean ordered,
- IIntentReceiver orderedResultTo, Bundle orderedExtras, int userId) {
+ IIntentReceiver resultTo, Bundle resultExtras, int userId) {
return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null,
callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
- AppOpsManager.OP_NONE, options, receivers, callerApp, orderedResultTo,
- Activity.RESULT_OK, null, orderedExtras, ordered, false, false, userId, false, null,
+ AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo,
+ Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId, false, null,
false, null);
}
@@ -1347,6 +1353,26 @@ public class BroadcastQueueTest {
}
/**
+ * Verify that we deliver results for unordered broadcasts.
+ */
+ @Test
+ public void testUnordered_ResultTo() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final IApplicationThread callerThread = callerApp.getThread();
+
+ final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), resultTo));
+
+ waitForIdle();
+ verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)),
+ eq(Activity.RESULT_OK), any(), any(), eq(false),
+ anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+ }
+
+ /**
* Verify that we're not surprised by a process attempting to finishing a
* broadcast when none is in progress.
*/
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index eb1314194aa3..ffacbf331d89 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -16,7 +16,7 @@
package com.android.server.biometrics.sensors;
-import static android.testing.TestableLooper.RunWithLooper;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -24,8 +24,10 @@ import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -35,6 +37,7 @@ import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.os.Binder;
@@ -63,28 +66,26 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.function.Supplier;
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest";
private static final int TEST_SENSOR_ID = 1;
private static final int LOG_NUM_RECENT_OPERATIONS = 2;
-
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getContext(), null);
private BiometricScheduler mScheduler;
private IBinder mToken;
-
@Mock
private IBiometricService mBiometricService;
- @Rule
- public final TestableContext mContext =
- new TestableContext(InstrumentationRegistry.getContext(), null);
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -323,7 +324,7 @@ public class BiometricSchedulerTest {
client1.getCallback().onClientFinished(client1, true /* success */);
waitForIdle();
verify(callback).onError(anyInt(), anyInt(),
- eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+ eq(BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
assertNull(mScheduler.getCurrentClient());
assertTrue(client1.isAlreadyDone());
@@ -484,7 +485,7 @@ public class BiometricSchedulerTest {
mScheduler.scheduleClientMonitor(interrupter);
waitForIdle();
- verify((Interruptable) interruptableMonitor).cancel();
+ verify(interruptableMonitor).cancel();
mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */);
}
@@ -500,7 +501,7 @@ public class BiometricSchedulerTest {
mScheduler.scheduleClientMonitor(interrupter);
waitForIdle();
- verify((Interruptable) interruptableMonitor, never()).cancel();
+ verify(interruptableMonitor, never()).cancel();
}
@Test
@@ -514,21 +515,180 @@ public class BiometricSchedulerTest {
assertTrue(client.mDestroyed);
}
+ @Test
+ public void testClearBiometricQueue_clearsHungAuthOperation() {
+ // Creating a hung client
+ final TestableLooper looper = TestableLooper.get(this);
+ final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
+ lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+ final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+
+ mScheduler.scheduleClientMonitor(client1, callback1);
+ waitForIdle();
+
+ mScheduler.startWatchdog();
+ waitForIdle();
+
+ //Checking client is hung
+ verify(callback1).onClientStarted(client1);
+ verify(callback1, never()).onClientFinished(any(), anyBoolean());
+ assertNotNull(mScheduler.mCurrentOperation);
+ assertEquals(0, mScheduler.getCurrentPendingCount());
+
+ looper.moveTimeForward(10000);
+ waitForIdle();
+ looper.moveTimeForward(3000);
+ waitForIdle();
+
+ // The hung client did not honor this operation, verify onError and authenticated
+ // were never called.
+ assertFalse(client1.mOnErrorCalled);
+ assertFalse(client1.mAuthenticateCalled);
+ verify(callback1).onClientFinished(client1, false /* success */);
+ assertNull(mScheduler.mCurrentOperation);
+ assertEquals(0, mScheduler.getCurrentPendingCount());
+ }
+
+ @Test
+ public void testAuthWorks_afterClearBiometricQueue() {
+ // Creating a hung client
+ final TestableLooper looper = TestableLooper.get(this);
+ final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
+ lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+ final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+
+ mScheduler.scheduleClientMonitor(client1, callback1);
+
+ assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
+ assertEquals(0, mScheduler.getCurrentPendingCount());
+
+ //Checking client is hung
+ waitForIdle();
+ verify(callback1, never()).onClientFinished(any(), anyBoolean());
+
+ //Start watchdog
+ mScheduler.startWatchdog();
+ waitForIdle();
+
+ // The watchdog should kick off the cancellation
+ looper.moveTimeForward(10000);
+ waitForIdle();
+ // After 10 seconds the HAL has 3 seconds to respond to a cancel
+ looper.moveTimeForward(3000);
+ waitForIdle();
+
+ // The hung client did not honor this operation, verify onError and authenticated
+ // were never called.
+ assertFalse(client1.mOnErrorCalled);
+ assertFalse(client1.mAuthenticateCalled);
+ verify(callback1).onClientFinished(client1, false /* success */);
+ assertEquals(0, mScheduler.getCurrentPendingCount());
+ assertNull(mScheduler.mCurrentOperation);
+
+
+ //Run additional auth client
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext,
+ lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+ final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
+
+ mScheduler.scheduleClientMonitor(client2, callback2);
+
+ assertEquals(client2, mScheduler.mCurrentOperation.getClientMonitor());
+ assertEquals(0, mScheduler.getCurrentPendingCount());
+
+ //Start watchdog
+ mScheduler.startWatchdog();
+ waitForIdle();
+ mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mock(ClientMonitorCallback.class));
+ waitForIdle();
+
+ //Ensure auth client passes
+ verify(callback2).onClientStarted(client2);
+ client2.getCallback().onClientFinished(client2, true);
+ waitForIdle();
+
+ looper.moveTimeForward(10000);
+ waitForIdle();
+ // After 10 seconds the HAL has 3 seconds to respond to a cancel
+ looper.moveTimeForward(3000);
+ waitForIdle();
+
+ //Asserting auth client passes
+ assertTrue(client2.isAlreadyDone());
+ assertNotNull(mScheduler.mCurrentOperation);
+ }
+
+ @Test
+ public void testClearBiometricQueue_doesNotClearOperationsWhenQueueNotStuck() {
+ //Creating clients
+ final TestableLooper looper = TestableLooper.get(this);
+ final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
+ lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+ final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+
+ mScheduler.scheduleClientMonitor(client1, callback1);
+ //Start watchdog
+ mScheduler.startWatchdog();
+ waitForIdle();
+ mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mock(ClientMonitorCallback.class));
+ mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mock(ClientMonitorCallback.class));
+ waitForIdle();
+
+ assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
+ assertEquals(2, mScheduler.getCurrentPendingCount());
+ verify(callback1, never()).onClientFinished(any(), anyBoolean());
+ verify(callback1).onClientStarted(client1);
+
+ //Client finishes successfully
+ client1.getCallback().onClientFinished(client1, true);
+ waitForIdle();
+
+ // The watchdog should kick off the cancellation
+ looper.moveTimeForward(10000);
+ waitForIdle();
+ // After 10 seconds the HAL has 3 seconds to respond to a cancel
+ looper.moveTimeForward(3000);
+ waitForIdle();
+
+ //Watchdog does not clear pending operations
+ assertEquals(1, mScheduler.getCurrentPendingCount());
+ assertNotNull(mScheduler.mCurrentOperation);
+
+ }
+
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
+ private void waitForIdle() {
+ TestableLooper.get(this).processAllMessages();
+ }
+
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
boolean mStartedHal = false;
boolean mStoppedHal = false;
boolean mDestroyed = false;
int mNumCancels = 0;
+ boolean mAuthenticateCalled = false;
+ boolean mOnErrorCalled = false;
- public TestAuthenticationClient(@NonNull Context context,
+ TestAuthenticationClient(@NonNull Context context,
@NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener) {
+ this(context, lazyDaemon, token, listener, 1 /* cookie */);
+ }
+
+ TestAuthenticationClient(@NonNull Context context,
+ @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener, int cookie) {
super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
- false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
+ false /* restricted */, TAG, cookie, false /* requireConfirmation */,
TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
true /* isStrongBiometric */, null /* taskStackListener */,
mock(LockoutTracker.class), false /* isKeyguard */,
@@ -546,7 +706,19 @@ public class BiometricSchedulerTest {
}
@Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {}
+ protected void handleLifecycleAfterAuth(boolean authenticated) {
+ }
+
+ @Override
+ public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+ boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
+ mAuthenticateCalled = true;
+ }
+
+ @Override
+ protected void onErrorInternal(int errorCode, int vendorCode, boolean finish) {
+ mOnErrorCalled = true;
+ }
@Override
public boolean wasUserDetected() {
@@ -651,8 +823,4 @@ public class BiometricSchedulerTest {
mDestroyed = true;
}
}
-
- private void waitForIdle() {
- TestableLooper.get(this).processAllMessages();
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c5ba360b6170..02bbe658f9b2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -587,9 +587,9 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final int keyCode = KeyEvent.KEYCODE_A;
final int action = VirtualKeyEvent.ACTION_UP;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */1, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
+
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -612,9 +612,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -627,9 +626,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -653,9 +651,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = -0.2f;
final float y = 0.7f;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build());
@@ -667,9 +664,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = -0.2f;
final float y = 0.7f;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -694,9 +690,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = 0.5f;
final float y = 1f;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
@@ -709,9 +704,8 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = 0.5f;
final float y = 1f;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -742,9 +736,8 @@ public class VirtualDeviceManagerServiceTest {
final float x = 100.5f;
final float y = 200.5f;
final int action = VirtualTouchEvent.ACTION_UP;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -761,9 +754,8 @@ public class VirtualDeviceManagerServiceTest {
final int action = VirtualTouchEvent.ACTION_UP;
final float pressure = 1.0f;
final float majorAxisSize = 10.0f;
- mInputController.mInputDeviceDescriptors.put(BINDER,
- new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1, PHYS, DEVICE_ID));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
.setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 6860abf40b56..062bde8f080b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
@@ -660,6 +661,117 @@ public class DisplayManagerServiceTest {
firstDisplayId);
}
+ /** Tests that the virtual device is created in a device display group. */
+ @Test
+ public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+ .thenReturn(true);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+
+ // Create a first virtual display. A display group should be created for this display on the
+ // virtual device.
+ final VirtualDisplayConfig.Builder builder1 =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group 1");
+
+ int displayId1 =
+ localService.createVirtualDisplay(
+ builder1.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+ // Create a second virtual display. This should be added to the previously created display
+ // group.
+ final VirtualDisplayConfig.Builder builder2 =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group 1");
+
+ int displayId2 =
+ localService.createVirtualDisplay(
+ builder2.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+ assertEquals(
+ "Both displays should be added to the same displayGroup.",
+ displayGroupId1,
+ displayGroupId2);
+ }
+
+ /**
+ * Tests that the virtual display is not added to the device display group when
+ * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
+ */
+ @Test
+ public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+ .thenReturn(true);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+
+ // Create a first virtual display. A display group should be created for this display on the
+ // virtual device.
+ final VirtualDisplayConfig.Builder builder1 =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group 1");
+
+ int displayId1 =
+ localService.createVirtualDisplay(
+ builder1.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+ // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
+ // the display should not be added to the previously created display group.
+ final VirtualDisplayConfig.Builder builder2 =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+ .setUniqueId("uniqueId --- device display group 1");
+
+ int displayId2 =
+ localService.createVirtualDisplay(
+ builder2.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+ assertNotEquals(
+ "Display 1 should be in the device display group and display 2 in its own display"
+ + " group.",
+ displayGroupId1,
+ displayGroupId2);
+ }
+
@Test
public void testGetDisplayIdToMirror() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 0b33c30fd7e8..657bda633ab5 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -369,6 +369,98 @@ public class LogicalDisplayMapperTest {
}
@Test
+ public void testDevicesAreAddedToDeviceDisplayGroups() {
+ // Create the default internal display of the device.
+ LogicalDisplay defaultDisplay =
+ add(
+ createDisplayDevice(
+ Display.TYPE_INTERNAL,
+ 600,
+ 800,
+ DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+
+ // Create 3 virtual displays associated with a first virtual device.
+ int deviceId1 = 1;
+ TestDisplayDevice display1 =
+ createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1);
+ LogicalDisplay virtualDevice1Display1 = add(display1);
+
+ TestDisplayDevice display2 =
+ createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1);
+ LogicalDisplay virtualDevice1Display2 = add(display2);
+
+ TestDisplayDevice display3 =
+ createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1);
+ LogicalDisplay virtualDevice1Display3 = add(display3);
+
+ // Create another 3 virtual displays associated with a second virtual device.
+ int deviceId2 = 2;
+ TestDisplayDevice display4 =
+ createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2);
+ LogicalDisplay virtualDevice2Display1 = add(display4);
+
+ TestDisplayDevice display5 =
+ createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2);
+ LogicalDisplay virtualDevice2Display2 = add(display5);
+
+ // The final display is created with FLAG_OWN_DISPLAY_GROUP set.
+ TestDisplayDevice display6 =
+ createDisplayDevice(
+ Display.TYPE_VIRTUAL,
+ "virtualDevice2Display3",
+ 600,
+ 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2);
+ LogicalDisplay virtualDevice2Display3 = add(display6);
+
+ // Verify that the internal display is in the default display group.
+ assertEquals(
+ DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay)));
+
+ // Verify that all the displays for virtual device 1 are in the same (non-default) display
+ // group.
+ int virtualDevice1DisplayGroupId =
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice1Display1));
+ assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId);
+ assertEquals(
+ virtualDevice1DisplayGroupId,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice1Display2)));
+ assertEquals(
+ virtualDevice1DisplayGroupId,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice1Display3)));
+
+ // The first 2 displays for virtual device 2 should be in the same non-default group.
+ int virtualDevice2DisplayGroupId =
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice2Display1));
+ assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId);
+ assertEquals(
+ virtualDevice2DisplayGroupId,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice2Display2)));
+ // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped
+ // with other displays of this device or be in the default display group.
+ assertNotEquals(
+ virtualDevice2DisplayGroupId,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice2Display3)));
+ assertNotEquals(
+ DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+ id(virtualDevice2Display3)));
+ }
+
+ @Test
public void testDeviceShouldBeWoken() {
assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
@@ -416,14 +508,22 @@ public class LogicalDisplayMapperTest {
/////////////////
private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
- return createDisplayDevice(new TestUtils.TestDisplayAddress(), type, width, height, flags);
+ return createDisplayDevice(
+ new TestUtils.TestDisplayAddress(), /* uniqueId */ "", type, width, height, flags);
+ }
+
+ private TestDisplayDevice createDisplayDevice(
+ int type, String uniqueId, int width, int height, int flags) {
+ return createDisplayDevice(
+ new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags);
}
private TestDisplayDevice createDisplayDevice(
- DisplayAddress address, int type, int width, int height, int flags) {
+ DisplayAddress address, String uniqueId, int type, int width, int height, int flags) {
TestDisplayDevice device = new TestDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
displayDeviceInfo.type = type;
+ displayDeviceInfo.uniqueId = uniqueId;
displayDeviceInfo.width = width;
displayDeviceInfo.height = height;
displayDeviceInfo.flags = flags;
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
new file mode 100644
index 000000000000..303a370b0ba9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.server.dreams;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.service.dreams.IDreamService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamControllerTest {
+ @Mock
+ private DreamController.Listener mListener;
+ @Mock
+ private Context mContext;
+ @Mock
+ private IBinder mIBinder;
+ @Mock
+ private IDreamService mIDreamService;
+
+ @Captor
+ private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor;
+ @Captor
+ private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final Handler mHandler = new Handler(mLooper.getLooper());
+
+ private DreamController mDreamController;
+
+ private Binder mToken;
+ private ComponentName mDreamName;
+ private ComponentName mOverlayName;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mIDreamService.asBinder()).thenReturn(mIBinder);
+ when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService);
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+
+ mToken = new Binder();
+ mDreamName = ComponentName.unflattenFromString("dream");
+ mOverlayName = ComponentName.unflattenFromString("dream_overlay");
+ mDreamController = new DreamController(mContext, mHandler, mListener);
+ }
+
+ @Test
+ public void startDream_attachOnServiceConnected() throws RemoteException {
+ // Call dream controller to start dreaming.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ // Mock service connected.
+ final ServiceConnection serviceConnection = captureServiceConnection();
+ serviceConnection.onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+
+ // Verify that dream service is called to attach.
+ verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any());
+ }
+
+ @Test
+ public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted()
+ throws RemoteException {
+ // Start first dream.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+ clearInvocations(mContext);
+
+ // Set up second dream.
+ final Binder newToken = new Binder();
+ final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream");
+ final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay");
+ final IDreamService newDreamService = mock(IDreamService.class);
+ final IBinder newBinder = mock(IBinder.class);
+ when(newDreamService.asBinder()).thenReturn(newBinder);
+ when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService);
+
+ // Start second dream.
+ mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(newDreamName, newBinder);
+ mLooper.dispatchAll();
+
+ // Mock second dream started.
+ verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/,
+ mRemoteCallbackCaptor.capture());
+ mRemoteCallbackCaptor.getValue().sendResult(null /*data*/);
+ mLooper.dispatchAll();
+
+ // Verify that the first dream is called to detach.
+ verify(mIDreamService).detach();
+ }
+
+ @Test
+ public void stopDream_detachFromService() throws RemoteException {
+ // Start dream.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+
+ // Stop dream.
+ mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/);
+
+ // Verify that dream service is called to detach.
+ verify(mIDreamService).detach();
+ }
+
+ private ServiceConnection captureServiceConnection() {
+ verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
+ any());
+ return mServiceConnectionACaptor.getValue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 545f3183ee26..3a57db9b975c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -19,7 +19,6 @@ package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_TV;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -47,7 +46,6 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.Collections;
/** Tests for {@link DevicePowerStatusAction} */
@@ -65,7 +63,6 @@ public class DevicePowerStatusActionTest {
private FakePowerManagerWrapper mPowerManager;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mPhysicalAddress;
private DevicePowerStatusAction mDevicePowerStatusAction;
@@ -79,7 +76,8 @@ public class DevicePowerStatusActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlService = new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
@@ -117,11 +115,8 @@ public class DevicePowerStatusActionTest {
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
- mPlaybackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- mPlaybackDevice.init();
- mLocalDevices.add(mPlaybackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ mPlaybackDevice = mHdmiControlService.playback();
mDevicePowerStatusAction = DevicePowerStatusAction.create(mPlaybackDevice, ADDR_TV,
mCallbackMock);
mTestLooper.dispatchAll();
@@ -213,7 +208,6 @@ public class DevicePowerStatusActionTest {
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
mTestLooper.dispatchAll();
@@ -238,7 +232,6 @@ public class DevicePowerStatusActionTest {
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
.buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
@@ -263,7 +256,6 @@ public class DevicePowerStatusActionTest {
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
.buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
@@ -293,6 +285,12 @@ public class DevicePowerStatusActionTest {
@Test
public void pendingActionDoesNotBlockSendingStandby() throws Exception {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+ mPhysicalAddress);
+ assertThat(mPlaybackDevice.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+
mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index eb7a76182054..7df007813b34 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -27,7 +27,6 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE;
import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_FOR_DEVICE_POWER_ON;
import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_FOR_REPORT_POWER_STATUS;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -86,7 +85,6 @@ public class DeviceSelectActionFromPlaybackTest {
private FakePowerManagerWrapper mPowerManager;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mPlaybackLogicalAddress1;
private int mPlaybackLogicalAddress2;
@@ -101,7 +99,8 @@ public class DeviceSelectActionFromPlaybackTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -119,8 +118,6 @@ public class DeviceSelectActionFromPlaybackTest {
};
- mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
- mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(mMyLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mNativeWrapper = new FakeNativeWrapper();
@@ -135,16 +132,14 @@ public class DeviceSelectActionFromPlaybackTest {
mHdmiCecController, mHdmiMhlControllerStub);
mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork);
- mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
-
+ mHdmiCecLocalDevicePlayback = mHdmiControlService.playback();
// The addresses depend on local device's LA.
// This help the tests to pass with every local device LA.
mPlaybackLogicalAddress1 =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 72d36b00d73d..ac57834ed5b0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -26,7 +26,6 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_DEVICE_POWER_ON;
import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_REPORT_POWER_STATUS;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -101,7 +100,6 @@ public class DeviceSelectActionFromTvTest {
private FakePowerManagerWrapper mPowerManager;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
@Before
public void setUp() {
@@ -110,7 +108,8 @@ public class DeviceSelectActionFromTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -127,8 +126,7 @@ public class DeviceSelectActionFromTvTest {
}
};
- mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mHdmiCecLocalDeviceTv.init();
+
mHdmiControlService.setIoLooper(mMyLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mNativeWrapper = new FakeNativeWrapper();
@@ -136,7 +134,6 @@ public class DeviceSelectActionFromTvTest {
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_1,
@@ -149,12 +146,12 @@ public class DeviceSelectActionFromTvTest {
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1);
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_2);
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
}
private static class TestActionTimer implements ActionTimer {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 9f744f9373ed..d2fe6da88830 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -19,7 +19,6 @@ import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +34,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
@@ -55,7 +55,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
-import java.util.ArrayList;
import java.util.Collections;
/**
@@ -68,7 +67,6 @@ public class HdmiCecAtomLoggingTest {
private HdmiCecAtomWriter mHdmiCecAtomWriterSpy;
private HdmiControlService mHdmiControlServiceSpy;
private HdmiCecController mHdmiCecController;
- private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
private HdmiMhlControllerStub mHdmiMhlControllerStub;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
@@ -77,7 +75,6 @@ public class HdmiCecAtomLoggingTest {
private Context mContextSpy;
private TestLooper mTestLooper = new TestLooper();
private int mPhysicalAddress = 0x1110;
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private HdmiPortInfo[] mHdmiPortInfo;
@Before
@@ -89,7 +86,8 @@ public class HdmiCecAtomLoggingTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -123,14 +121,9 @@ public class HdmiCecAtomLoggingTest {
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
- mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
- mHdmiCecLocalDevicePlayback.init();
- mLocalDevices.add(mHdmiCecLocalDevicePlayback);
-
mHdmiControlServiceSpy.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 91d265c81083..08d0e9053e2b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -48,7 +48,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
-import java.util.Collections;
@SmallTest
@Presubmit
@@ -80,15 +79,19 @@ public class HdmiCecLocalDeviceAudioSystemTest {
private HdmiDeviceInfo mDeviceInfo;
private boolean mArcSupport;
private HdmiPortInfo[] mHdmiPortInfo;
+ private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
@Before
public void setUp() {
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ mLocalDeviceTypes,
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index fe9e0b6a2d07..75c4d92ab9f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -47,6 +47,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.concurrent.TimeUnit;
@SmallTest
@@ -78,7 +79,6 @@ public class HdmiCecLocalDevicePlaybackTest {
private TestLooper mTestLooper = new TestLooper();
private FakePowerManagerWrapper mPowerManager;
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
private int mPlaybackPhysicalAddress;
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
@@ -91,10 +91,10 @@ public class HdmiCecLocalDevicePlaybackTest {
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
- mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- mLocalDeviceTypes, new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
@@ -128,8 +128,6 @@ public class HdmiCecLocalDevicePlaybackTest {
}
};
- mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
- mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
@@ -137,7 +135,6 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDevicePlayback);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
@@ -148,10 +145,11 @@ public class HdmiCecLocalDevicePlaybackTest {
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPlaybackPhysicalAddress = 0x2000;
mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
mTestLooper.dispatchAll();
+ mHdmiCecLocalDevicePlayback = mHdmiControlService.playback();
+ mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mPlaybackLogicalAddress = mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
mNativeWrapper.clearResultMessages();
@@ -1108,7 +1106,11 @@ public class HdmiCecLocalDevicePlaybackTest {
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
mPowerManager.setInteractive(true);
- HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+ .isEqualTo(Constants.HANDLED);
+ message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 8112ca8fbb14..7a2a5838134c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -128,7 +128,8 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -165,8 +166,6 @@ public class HdmiCecLocalDeviceTvTest {
}
};
- mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(mMyLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mNativeWrapper = new FakeNativeWrapper();
@@ -174,7 +173,6 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
@@ -185,11 +183,12 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTvPhysicalAddress = 0x0000;
mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
mTestLooper.dispatchAll();
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
+ mLocalDevices.add(mHdmiCecLocalDeviceTv);
for (String sad : SADS_NOT_TO_QUERY) {
mHdmiControlService.getHdmiCecConfig().setIntValue(
sad, HdmiControlManager.QUERY_SAD_DISABLED);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index b94deeddb8af..a08e398b4010 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -16,7 +16,6 @@
package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -25,6 +24,7 @@ import static org.mockito.Mockito.spy;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.os.Looper;
import android.os.test.TestLooper;
@@ -40,7 +40,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.ArrayList;
import java.util.Collections;
@SmallTest
@@ -57,7 +56,6 @@ public class HdmiCecPowerStatusControllerTest {
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private HdmiControlService mHdmiControlService;
private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
@@ -66,7 +64,8 @@ public class HdmiCecPowerStatusControllerTest {
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
+ mHdmiControlService = new HdmiControlService(contextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
@@ -90,9 +89,6 @@ public class HdmiCecPowerStatusControllerTest {
};
mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(myLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(contextSpy));
mNativeWrapper = new FakeNativeWrapper();
@@ -100,7 +96,6 @@ public class HdmiCecPowerStatusControllerTest {
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDevicePlayback);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
@@ -111,10 +106,9 @@ public class HdmiCecPowerStatusControllerTest {
mPowerManager = new FakePowerManagerWrapper(contextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x2000);
mTestLooper.dispatchAll();
-
+ mHdmiCecLocalDevicePlayback = mHdmiControlService.playback();
mHdmiCecPowerStatusController = new HdmiCecPowerStatusController(mHdmiControlService);
mNativeWrapper.clearResultMessages();
}
@@ -254,7 +248,6 @@ public class HdmiCecPowerStatusControllerTest {
private void setCecVersion(int version) {
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, version);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 674e47168392..1b867be81669 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
@@ -61,7 +62,6 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Optional;
/**
@@ -84,14 +84,17 @@ public class HdmiControlServiceTest {
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private HdmiPortInfo[] mHdmiPortInfo;
+ private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
@Before
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+ mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mLocalDeviceTypes.add(DEVICE_AUDIO_SYSTEM);
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, mLocalDeviceTypes,
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -228,8 +231,6 @@ public class HdmiControlServiceTest {
mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mNativeWrapper.clearResultMessages();
-
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
@@ -461,7 +462,6 @@ public class HdmiControlServiceTest {
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -480,7 +480,6 @@ public class HdmiControlServiceTest {
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -502,7 +501,6 @@ public class HdmiControlServiceTest {
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
@@ -519,7 +517,6 @@ public class HdmiControlServiceTest {
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 1fa3871347f8..9b8cedfa6234 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -88,7 +88,8 @@ public class OneTouchPlayActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlService = new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
@@ -142,11 +143,7 @@ public class OneTouchPlayActionTest {
public void succeedWithUnknownTvDevice() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
@@ -191,11 +188,7 @@ public class OneTouchPlayActionTest {
public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -244,11 +237,7 @@ public class OneTouchPlayActionTest {
public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -310,11 +299,7 @@ public class OneTouchPlayActionTest {
public void timeOut_Cec14b() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -359,11 +344,7 @@ public class OneTouchPlayActionTest {
@Test
public void succeedIfPowerStatusOn_Cec20() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -399,11 +380,8 @@ public class OneTouchPlayActionTest {
@Test
public void succeedIfPowerStatusUnknown_Cec20() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -453,11 +431,7 @@ public class OneTouchPlayActionTest {
@Test
public void succeedIfPowerStatusStandby_Cec20() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
@@ -510,11 +484,6 @@ public class OneTouchPlayActionTest {
assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
-
TestCallback callback = new TestCallback();
mHdmiControlService.oneTouchPlay(callback);
@@ -524,9 +493,8 @@ public class OneTouchPlayActionTest {
mNativeWrapper.clearResultMessages();
setHdmiControlEnabled(true);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-
mTestLooper.dispatchAll();
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
HdmiCecMessage reportPowerStatusMessage =
HdmiCecMessageBuilder.buildReportPowerStatus(
@@ -554,12 +522,7 @@ public class OneTouchPlayActionTest {
public void succeedWithAddressAllocated_Cec14b() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
-
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
@@ -632,11 +595,7 @@ public class OneTouchPlayActionTest {
public void noWakeUpOnReportPowerStatus() throws Exception {
setUp(true);
- HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
- mHdmiControlService);
- playbackDevice.init();
- mLocalDevices.add(playbackDevice);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index e5058bea8d1b..f72ac713f5b4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -20,7 +20,6 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -44,7 +43,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@@ -60,7 +58,6 @@ public class PowerStatusMonitorActionTest {
private FakePowerManagerWrapper mPowerManager;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mPhysicalAddress;
private HdmiCecLocalDeviceTv mTvDevice;
@@ -101,9 +98,6 @@ public class PowerStatusMonitorActionTest {
this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mTvDevice.init();
- mLocalDevices.add(mTvDevice);
mTestLooper.dispatchAll();
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
hdmiPortInfo[0] =
@@ -117,8 +111,8 @@ public class PowerStatusMonitorActionTest {
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x0000;
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
+ mTvDevice = mHdmiControlService.tv();
mNativeWrapper.clearResultMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index c2519caabfee..c07d4be78047 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -18,12 +18,12 @@ package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -69,7 +69,6 @@ public class RequestSadActionTest {
private FakePowerManagerWrapper mPowerManager;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mTvLogicalAddress;
private List<byte[]> mSupportedSads;
private RequestSadCallback mCallback =
@@ -97,7 +96,7 @@ public class RequestSadActionTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
- new HdmiControlService(context, Collections.emptyList(),
+ new HdmiControlService(context, Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
@@ -115,8 +114,6 @@ public class RequestSadActionTest {
}
};
- mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(mMyLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mNativeWrapper = new FakeNativeWrapper();
@@ -124,14 +121,13 @@ public class RequestSadActionTest {
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDeviceTv);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
mNativeWrapper.clearResultMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 566a7e0fecce..f5bf30b1ec1e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -25,7 +25,6 @@ import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE;
import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION;
import static com.google.common.truth.Truth.assertThat;
@@ -134,7 +133,6 @@ public class RoutingControlActionTest {
private FakePowerManagerWrapper mPowerManager;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice,
TestInputSelectCallback callback) {
@@ -150,7 +148,8 @@ public class RoutingControlActionTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -172,15 +171,12 @@ public class RoutingControlActionTest {
}
};
- mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR,
@@ -190,9 +186,9 @@ public class RoutingControlActionTest {
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index dadf81571e30..e3c8939c81e5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -21,7 +21,6 @@ import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -67,7 +66,6 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
private Context mContextSpy;
private TestLooper mTestLooper = new TestLooper();
private int mPhysicalAddress = 0x1100;
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mPlaybackLogicalAddress;
private TestCallback mTestCallback;
@@ -82,7 +80,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -104,21 +103,16 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
- mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
- mPlaybackDevice.init();
- mLocalDevices.add(mPlaybackDevice);
-
- mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mTestLooper.dispatchAll();
+ mPlaybackDevice = mHdmiControlServiceSpy.playback();
mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress();
// Setup specific to these tests
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV));
mTestLooper.dispatchAll();
-
mTestCallback = new TestCallback();
mAction = new SetAudioVolumeLevelDiscoveryAction(mPlaybackDevice,
Constants.ADDR_TV, mTestCallback);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 1644252e5739..e7557fe3aa43 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -19,7 +19,6 @@ package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -28,6 +27,7 @@ import static org.mockito.Mockito.spy;
import android.content.Context;
import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.media.AudioManager;
import android.os.Looper;
@@ -42,7 +42,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.ArrayList;
import java.util.Collections;
/**
@@ -61,7 +60,6 @@ public class SystemAudioAutoInitiationActionTest {
private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private int mPhysicalAddress;
@Before
@@ -70,7 +68,8 @@ public class SystemAudioAutoInitiationActionTest {
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ mHdmiControlService = new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
@@ -94,15 +93,12 @@ public class SystemAudioAutoInitiationActionTest {
}
};
- mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
- mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(myLooper);
mNativeWrapper = new FakeNativeWrapper();
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mLocalDevices.add(mHdmiCecLocalDeviceTv);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
@@ -113,10 +109,10 @@ public class SystemAudioAutoInitiationActionTest {
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mPhysicalAddress = 0x0000;
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mTestLooper.dispatchAll();
+ mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
mPhysicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
mNativeWrapper.clearResultMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 65076a372b0b..c68db3460dac 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -19,6 +19,7 @@ package com.android.server.input
import android.content.Context
import android.content.ContextWrapper
import android.hardware.BatteryState.STATUS_CHARGING
+import android.hardware.BatteryState.STATUS_DISCHARGING
import android.hardware.BatteryState.STATUS_FULL
import android.hardware.BatteryState.STATUS_UNKNOWN
import android.hardware.input.IInputDeviceBatteryListener
@@ -32,6 +33,7 @@ import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
import androidx.test.InstrumentationRegistry
+import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
import com.android.server.input.BatteryController.UEventManager
import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
import org.hamcrest.Description
@@ -42,6 +44,8 @@ import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsEqual.equalTo
import org.junit.After
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
@@ -63,14 +67,20 @@ import org.mockito.hamcrest.MockitoHamcrest
import org.mockito.junit.MockitoJUnit
import org.mockito.verification.VerificationMode
-private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputDevice =
+private fun createInputDevice(
+ deviceId: Int,
+ hasBattery: Boolean = true,
+ supportsUsi: Boolean = false,
+ generation: Int = -1,
+): InputDevice =
InputDevice.Builder()
.setId(deviceId)
.setName("Device $deviceId")
.setDescriptor("descriptor $deviceId")
.setExternal(true)
.setHasBattery(hasBattery)
- .setGeneration(0)
+ .setSupportsUsi(supportsUsi)
+ .setGeneration(generation)
.build()
// Returns a matcher that helps match member variables of a class.
@@ -118,7 +128,10 @@ private fun matchesState(
return Matchers.allOf(batteryStateMatchers)
}
-// Helper used to verify interactions with a mocked battery listener.
+private fun isInvalidBatteryState(deviceId: Int): Matcher<IInputDeviceBatteryState> =
+ matchesState(deviceId, isPresent = false, status = STATUS_UNKNOWN, capacity = Float.NaN)
+
+// Helpers used to verify interactions with a mocked battery listener.
private fun IInputDeviceBatteryListener.verifyNotified(
deviceId: Int,
mode: VerificationMode = times(1),
@@ -127,8 +140,21 @@ private fun IInputDeviceBatteryListener.verifyNotified(
capacity: Float? = null,
eventTime: Long? = null
) {
- verify(this, mode).onBatteryStateChanged(
- MockitoHamcrest.argThat(matchesState(deviceId, isPresent, status, capacity, eventTime)))
+ verifyNotified(matchesState(deviceId, isPresent, status, capacity, eventTime), mode)
+}
+
+private fun IInputDeviceBatteryListener.verifyNotified(
+ matcher: Matcher<IInputDeviceBatteryState>,
+ mode: VerificationMode = times(1)
+) {
+ verify(this, mode).onBatteryStateChanged(MockitoHamcrest.argThat(matcher))
+}
+
+private fun createMockListener(): IInputDeviceBatteryListener {
+ val listener = mock(IInputDeviceBatteryListener::class.java)
+ val binder = mock(Binder::class.java)
+ `when`(listener.asBinder()).thenReturn(binder)
+ return listener
}
/**
@@ -143,6 +169,8 @@ class BatteryControllerTests {
const val PID = 42
const val DEVICE_ID = 13
const val SECOND_DEVICE_ID = 11
+ const val USI_DEVICE_ID = 101
+ const val SECOND_USI_DEVICE_ID = 102
const val TIMESTAMP = 123456789L
}
@@ -168,10 +196,11 @@ class BatteryControllerTests {
testLooper = TestLooper()
val inputManager = InputManager.resetInstance(iInputManager)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
- `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID))
- `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
- .thenReturn(createInputDevice(SECOND_DEVICE_ID))
+ `when`(iInputManager.inputDeviceIds).then {
+ deviceGenerationMap.keys.toIntArray()
+ }
+ addInputDevice(DEVICE_ID)
+ addInputDevice(SECOND_DEVICE_ID)
batteryController = BatteryController(context, native, testLooper.looper, uEventManager)
batteryController.systemRunning()
@@ -180,10 +209,30 @@ class BatteryControllerTests {
devicesChangedListener = listenerCaptor.value
}
- private fun notifyDeviceChanged(deviceId: Int) {
- deviceGenerationMap[deviceId] = deviceGenerationMap[deviceId]?.plus(1) ?: 1
+ private fun notifyDeviceChanged(
+ deviceId: Int,
+ hasBattery: Boolean = true,
+ supportsUsi: Boolean = false
+ ) {
+ val generation = deviceGenerationMap[deviceId]?.plus(1)
+ ?: throw IllegalArgumentException("Device $deviceId was never added!")
+ deviceGenerationMap[deviceId] = generation
+
+ `when`(iInputManager.getInputDevice(deviceId))
+ .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation))
val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
- devicesChangedListener.onInputDevicesChanged(list.toIntArray())
+ if (::devicesChangedListener.isInitialized) {
+ devicesChangedListener.onInputDevicesChanged(list.toIntArray())
+ }
+ }
+
+ private fun addInputDevice(
+ deviceId: Int,
+ hasBattery: Boolean = true,
+ supportsUsi: Boolean = false
+ ) {
+ deviceGenerationMap[deviceId] = 0
+ notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
}
@After
@@ -191,13 +240,6 @@ class BatteryControllerTests {
InputManager.clearInstance()
}
- private fun createMockListener(): IInputDeviceBatteryListener {
- val listener = mock(IInputDeviceBatteryListener::class.java)
- val binder = mock(Binder::class.java)
- `when`(listener.asBinder()).thenReturn(binder)
- return listener
- }
-
@Test
fun testRegisterAndUnregisterBinderLifecycle() {
val listener = createMockListener()
@@ -303,19 +345,14 @@ class BatteryControllerTests {
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
// If the battery presence for the InputDevice changes, the listener is notified.
- `when`(iInputManager.getInputDevice(DEVICE_ID))
- .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false))
- notifyDeviceChanged(DEVICE_ID)
+ notifyDeviceChanged(DEVICE_ID, hasBattery = false)
testLooper.dispatchNext()
- listener.verifyNotified(DEVICE_ID, isPresent = false, status = STATUS_UNKNOWN,
- capacity = Float.NaN)
+ listener.verifyNotified(isInvalidBatteryState(DEVICE_ID))
// Since the battery is no longer present, the UEventListener should be removed.
verify(uEventManager).removeListener(uEventListener.value)
// If the battery becomes present again, the listener is notified.
- `when`(iInputManager.getInputDevice(DEVICE_ID))
- .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true))
- notifyDeviceChanged(DEVICE_ID)
+ notifyDeviceChanged(DEVICE_ID, hasBattery = true)
testLooper.dispatchNext()
listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING,
capacity = 0.78f)
@@ -340,9 +377,17 @@ class BatteryControllerTests {
// Move the time forward so that the polling period has elapsed.
// The listener should be notified.
- testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS - 1)
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS - 1)
+ assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
testLooper.dispatchNext()
listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
+
+ // Move the time forward so that another polling period has elapsed.
+ // The battery should still be polled, but there is no change so listeners are not notified.
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+ assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
+ testLooper.dispatchNext()
+ listener.verifyNotified(DEVICE_ID, mode = times(1), capacity = 0.80f)
}
@Test
@@ -357,7 +402,8 @@ class BatteryControllerTests {
// The battery state changed, but we should not be polling for battery changes when the
// device is not interactive.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
- testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+ assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
testLooper.dispatchAll()
listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
@@ -368,7 +414,8 @@ class BatteryControllerTests {
// Ensure that we continue to poll for battery changes.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90)
- testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+ assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
testLooper.dispatchNext()
listener.verifyNotified(DEVICE_ID, capacity = 0.90f)
}
@@ -398,4 +445,93 @@ class BatteryControllerTests {
matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f))
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)
}
+
+ @Test
+ fun testUsiDeviceIsMonitoredPersistently() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+
+ // Even though there is no listener added for this device, it is being monitored.
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // Add and remove a listener for the device.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ batteryController.unregisterBatteryListener(USI_DEVICE_ID, listener, PID)
+
+ // The device is still being monitored.
+ verify(uEventManager, never()).removeListener(uEventListener.value)
+ }
+
+ @Test
+ fun testNoPollingWhenUsiDevicesAreMonitored() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ `when`(native.getBatteryDevicePath(SECOND_USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device2")
+ addInputDevice(SECOND_USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+ assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
+
+ // Add a listener.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+
+ testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+ assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
+ }
+
+ @Test
+ fun testExpectedFlowForUsiBattery() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // A USI device's battery state is not valid until the first UEvent notification.
+ // Add a listener, and ensure it is notified that the battery state is not present.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+
+ // Ensure that querying for battery state also returns the same invalid result.
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // There is a UEvent signaling a battery change. The battery state is now valid.
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+ // There is another UEvent notification. The battery state is now updated.
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(64)
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
+
+ // The battery state is still valid after a millisecond.
+ testLooper.moveTimeForward(1)
+ testLooper.dispatchAll()
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
+
+ // The battery is no longer present after the timeout expires.
+ testLooper.moveTimeForward(BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
+ testLooper.dispatchNext()
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 3bcde6a3aa53..b7f90d43881b 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -113,8 +113,10 @@ import android.app.NotificationManager;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
@@ -134,6 +136,7 @@ import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
@@ -152,6 +155,7 @@ import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.DataUnit;
import android.util.Log;
import android.util.Pair;
@@ -171,11 +175,12 @@ import com.android.server.LocalServices;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.usage.AppStandbyInternal;
-import libcore.io.Streams;
-
import com.google.common.util.concurrent.AbstractFuture;
+import libcore.io.Streams;
+
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -286,6 +291,8 @@ public class NetworkPolicyManagerServiceTest {
private NetworkPolicyListenerAnswer mPolicyListener;
private NetworkPolicyManagerService mService;
+ private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+
/**
* In some of the tests while initializing NetworkPolicyManagerService,
* ACTION_RESTRICT_BACKGROUND_CHANGED is broadcasted. This is for capturing that broadcast.
@@ -437,6 +444,21 @@ public class NetworkPolicyManagerServiceTest {
public void enforceCallingOrSelfPermission(String permission, String message) {
// Assume that we're AID_SYSTEM
}
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mRegisteredReceivers.add(receiver);
+ return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+ }
+
+ @Override
+ public Intent registerReceiverForAllUsers(BroadcastReceiver receiver,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mRegisteredReceivers.add(receiver);
+ return super.registerReceiverForAllUsers(receiver, filter, broadcastPermission,
+ scheduler);
+ }
};
setNetpolicyXml(context);
@@ -557,6 +579,13 @@ public class NetworkPolicyManagerServiceTest {
RecurrenceRule.sClock = Clock.systemDefaultZone();
}
+ @After
+ public void unregisterReceivers() throws Exception {
+ for (BroadcastReceiver receiver : mRegisteredReceivers) {
+ mServiceContext.unregisterReceiver(receiver);
+ }
+ }
+
@Test
public void testTurnRestrictBackgroundOn() throws Exception {
assertRestrictBackgroundOff();
@@ -2033,6 +2062,9 @@ public class NetworkPolicyManagerServiceTest {
@Test
public void testNormalizeTemplate_duplicatedMergedImsiList() {
+ // This test leads to a Log.wtf, so skip it on eng builds. Otherwise, Log.wtf() would
+ // result in this process getting killed.
+ Assume.assumeFalse(Build.IS_ENG);
final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
.setSubscriberIds(Set.of(TEST_IMSI)).build();
final String[] mergedImsiGroup = new String[] {TEST_IMSI, TEST_IMSI};
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index fe4db3a758e3..db2630e2683c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -87,7 +87,6 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.compat.PlatformCompat;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.BatteryReceiver;
@@ -147,7 +146,6 @@ public class PowerManagerServiceTest {
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
@Mock private AppOpsManager mAppOpsManagerMock;
@Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
- @Mock private PlatformCompat mPlatformCompat;
@Mock
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@@ -321,11 +319,6 @@ public class PowerManagerServiceTest {
AppOpsManager createAppOpsManager(Context context) {
return mAppOpsManagerMock;
}
-
- @Override
- PlatformCompat createPlatformCompat(Context context) {
- return mPlatformCompat;
- }
});
return mService;
}
@@ -505,9 +498,6 @@ public class PowerManagerServiceTest {
String packageName = "pkg.name";
when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mPlatformCompat.isChangeEnabledByPackageName(
- eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
- anyInt())).thenReturn(true);
when(mContextSpy.checkCallingOrSelfPermission(
android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -532,23 +522,6 @@ public class PowerManagerServiceTest {
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-
- // Verify that on older platforms only the appOp is necessary and the permission isn't
- // checked
- when(mPlatformCompat.isChangeEnabledByPackageName(
- eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
- anyInt())).thenReturn(false);
- when(mContextSpy.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
- PackageManager.PERMISSION_DENIED);
- forceSleep();
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
- flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
- mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
}
@Test
@@ -568,7 +541,7 @@ public class PowerManagerServiceTest {
int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
+ if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
} else {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
@@ -577,9 +550,6 @@ public class PowerManagerServiceTest {
when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mPlatformCompat.isChangeEnabledByPackageName(
- eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
- anyInt())).thenReturn(true);
when(mContextSpy.checkCallingOrSelfPermission(
android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -589,7 +559,7 @@ public class PowerManagerServiceTest {
flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
+ if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
} else {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
index 52e9d3a06fe2..34d008201bb2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -17,6 +17,7 @@
package com.android.server.timezonedetector.location;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
/**
* Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
@@ -31,7 +32,8 @@ public final class FakeTimeZoneProviderEventPreProcessor
public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
if (mIsUncertain) {
return TimeZoneProviderEvent.createUncertainEvent(
- timeZoneProviderEvent.getCreationElapsedMillis());
+ timeZoneProviderEvent.getCreationElapsedMillis(),
+ TimeZoneProviderStatus.UNKNOWN);
}
return timeZoneProviderEvent;
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index 0257ce0fe7b9..ed426cdc9f7e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -15,6 +15,12 @@
*/
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+
import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
@@ -48,6 +54,7 @@ import android.annotation.Nullable;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -78,8 +85,15 @@ public class LocationTimeZoneProviderControllerTest {
createSuggestionEvent(asList("Europe/London"));
private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
createSuggestionEvent(asList("Europe/Paris"));
+ private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_UNKNOWN)
+ .build();
private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
- TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_TIME_MILLIS);
+ TimeZoneProviderEvent.createUncertainEvent(
+ ARBITRARY_TIME_MILLIS, UNCERTAIN_PROVIDER_STATUS);
private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test");
@@ -1390,12 +1404,17 @@ public class LocationTimeZoneProviderControllerTest {
}
private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
+ TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setConnectivityStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .build();
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+ .setTimeZoneIds(timeZoneIds)
+ .build();
return TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_TIME_MILLIS,
- new TimeZoneProviderSuggestion.Builder()
- .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
- .setTimeZoneIds(timeZoneIds)
- .build());
+ ARBITRARY_TIME_MILLIS, suggestion, providerStatus);
}
private static void assertControllerState(LocationTimeZoneProviderController controller,
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index cb2905d2266a..8429fa4d18d1 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -33,6 +33,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -120,8 +121,9 @@ public class LocationTimeZoneProviderTest {
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
+ TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion);
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, providerStatus);
provider.simulateProviderEventReceived(event);
currentState = assertAndReturnProviderState(
@@ -133,7 +135,8 @@ public class LocationTimeZoneProviderTest {
mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS);
+ event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
+ TimeZoneProviderStatus.UNKNOWN);
provider.simulateProviderEventReceived(event);
currentState = assertAndReturnProviderState(
@@ -193,12 +196,13 @@ public class LocationTimeZoneProviderTest {
.setTimeZoneIds(Arrays.asList("Europe/London"))
.build();
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion);
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, TimeZoneProviderStatus.UNKNOWN);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
// Simulate an uncertain event being received.
- event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS);
+ event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
+ TimeZoneProviderStatus.UNKNOWN);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -235,8 +239,9 @@ public class LocationTimeZoneProviderTest {
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
.setTimeZoneIds(invalidTimeZoneIds)
.build();
+ TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion);
+ ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion, providerStatus);
provider.simulateProviderEventReceived(event);
provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index ab4fe2938bcf..c4786043cc29 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -16,10 +16,15 @@
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+
import static com.google.common.truth.Truth.assertWithMessage;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
import android.service.timezone.TimeZoneProviderSuggestion;
import org.junit.Test;
@@ -54,8 +59,14 @@ public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
for (String timeZone : nonExistingTimeZones) {
TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ TimeZoneProviderStatus expectedProviderStatus =
+ new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+ .build();
+
TimeZoneProviderEvent expectedResultEvent =
- TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis());
+ TimeZoneProviderEvent.createUncertainEvent(
+ event.getCreationElapsedMillis(), expectedProviderStatus);
assertWithMessage(timeZone + " is not a valid time zone")
.that(mPreProcessor.preProcess(event))
.isEqualTo(expectedResultEvent);
@@ -63,12 +74,17 @@ public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
}
private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
+ TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
+ .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
+ .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+ .build();
+ TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+ .build();
return TimeZoneProviderEvent.createSuggestionEvent(
- ARBITRARY_TIME_MILLIS,
- new TimeZoneProviderSuggestion.Builder()
- .setTimeZoneIds(Arrays.asList(timeZoneIds))
- .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
- .build());
+ ARBITRARY_TIME_MILLIS, suggestion, providerStatus);
}
}
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 c3d49e1e5152..bc319db94997 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -242,7 +242,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted() {
+ public void onBackStarted(BackEvent backEvent) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 11ae5d4abaf8..e69418ba908e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2314,6 +2314,8 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(displayWidth, windowConfig.getBounds().width());
assertEquals(displayHeight, windowConfig.getBounds().height());
assertEquals(windowingMode, windowConfig.getWindowingMode());
+ assertEquals(Configuration.SCREENLAYOUT_SIZE_NORMAL,
+ config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);
// test misc display overrides
assertEquals(ignoreOrientationRequests, testDisplayContent.mSetIgnoreOrientationRequest);
@@ -2355,6 +2357,8 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(displayWidth, windowConfig.getBounds().width());
assertEquals(displayHeight, windowConfig.getBounds().height());
assertEquals(windowingMode, windowConfig.getWindowingMode());
+ assertEquals(Configuration.SCREENLAYOUT_SIZE_LARGE, testDisplayContent
+ .getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);
// test misc display overrides
assertEquals(ignoreOrientationRequests, testDisplayContent.mSetIgnoreOrientationRequest);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
index fc3962bd0b23..cd4d65d7dab1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
@@ -26,8 +26,10 @@ import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
-import android.view.Surface;
import android.platform.test.annotations.Presubmit;
+import android.view.Surface;
+
+import com.android.internal.policy.TransitionAnimation;
import org.junit.Before;
import org.junit.Test;
@@ -52,7 +54,8 @@ public class RotationAnimationUtilsTest {
public void blackLuma() {
Bitmap swBitmap = createBitmap(0);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
+ float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
+
assertEquals(0, borderLuma, 0);
}
@@ -60,7 +63,7 @@ public class RotationAnimationUtilsTest {
public void whiteLuma() {
Bitmap swBitmap = createBitmap(1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
+ float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}
@@ -68,7 +71,7 @@ public class RotationAnimationUtilsTest {
public void unevenBitmapDimens() {
Bitmap swBitmap = createBitmap(1, BITMAP_WIDTH + 1, BITMAP_HEIGHT + 1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
+ float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}
@@ -77,7 +80,7 @@ public class RotationAnimationUtilsTest {
Bitmap swBitmap = createBitmap(1);
setBorderLuma(swBitmap, 0);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
+ float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(0, borderLuma, 0);
}
@@ -86,7 +89,7 @@ public class RotationAnimationUtilsTest {
Bitmap swBitmap = createBitmap(0);
setBorderLuma(swBitmap, 1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
- float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
+ float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}
diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 2ae328b0d8e9..394d6e774aa1 100644
--- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -19,6 +19,7 @@ package com.android.server.usb;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
@@ -76,10 +77,10 @@ public final class UsbDirectMidiDevice implements Closeable {
// event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
- // Arbitrary number for timeout to not continue sending to
- // an inactive device. This number tries to balances the number
- // of cycles and not being permanently stuck.
- private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 10;
+ // Timeout for sending a packet to a device.
+ // If bulkTransfer times out, retry sending the packet up to 20 times.
+ private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
+ private static final int BULK_TRANSFER_NUMBER_OF_RETRIES = 20;
// Arbitrary number for timeout when closing a thread
private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
@@ -386,10 +387,15 @@ public final class UsbDirectMidiDevice implements Closeable {
break;
}
final UsbRequest response = connectionFinal.requestWait();
- if (response != request) {
- Log.w(TAG, "Unexpected response");
+ if (response == null) {
+ Log.w(TAG, "Response is null");
break;
}
+ if (request != response) {
+ Log.w(TAG, "Skipping response");
+ continue;
+ }
+
int bytesRead = byteBuffer.position();
if (bytesRead > 0) {
@@ -513,9 +519,47 @@ public final class UsbDirectMidiDevice implements Closeable {
convertedArray.length);
}
- connectionFinal.bulkTransfer(endpointFinal, convertedArray,
- convertedArray.length,
- BULK_TRANSFER_TIMEOUT_MILLISECONDS);
+ boolean isInterrupted = false;
+ // Split the packet into multiple if they are greater than the
+ // endpoint's max packet size.
+ for (int curPacketStart = 0;
+ curPacketStart < convertedArray.length &&
+ isInterrupted == false;
+ curPacketStart += endpointFinal.getMaxPacketSize()) {
+ int transferResult = -1;
+ int retryCount = 0;
+ int curPacketSize = Math.min(endpointFinal.getMaxPacketSize(),
+ convertedArray.length - curPacketStart);
+
+ // Keep trying to send the packet until the result is
+ // successful or until the retry limit is reached.
+ while (transferResult < 0 && retryCount <=
+ BULK_TRANSFER_NUMBER_OF_RETRIES) {
+ transferResult = connectionFinal.bulkTransfer(
+ endpointFinal,
+ convertedArray,
+ curPacketStart,
+ curPacketSize,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
+ retryCount++;
+
+ if (Thread.currentThread().interrupted()) {
+ Log.w(TAG, "output thread interrupted after send");
+ isInterrupted = true;
+ break;
+ }
+ if (transferResult < 0) {
+ Log.d(TAG, "retrying packet. retryCount = "
+ + retryCount + " result = " + transferResult);
+ if (retryCount > BULK_TRANSFER_NUMBER_OF_RETRIES) {
+ Log.w(TAG, "Skipping packet because timeout");
+ }
+ }
+ }
+ }
+ if (isInterrupted == true) {
+ break;
+ }
eventSchedulerFinal.addEventToPool(event);
}
} catch (NullPointerException e) {
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 5179babbd31d..76d2b7d8fce5 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -206,6 +206,8 @@ public final class TelephonyUtils {
return "DATA_ON_NON_DEFAULT_DURING_VOICE_CALL";
case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
return "MMS_ALWAYS_ALLOWED";
+ case TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH:
+ return "AUTO_DATA_SWITCH";
default:
return "UNKNOWN(" + mobileDataPolicy + ")";
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fcb76b3920fe..d314a6579b58 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8715,6 +8715,8 @@ public class CarrierConfigManager {
* premium capabilities should be blocked when
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* returns a failure due to user action or timeout.
+ * The maximum number of network boost notifications to show the user are defined in
+ * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
*
* The default value is 30 minutes.
*
@@ -8726,6 +8728,22 @@ public class CarrierConfigManager {
"premium_capability_notification_backoff_hysteresis_time_millis_long";
/**
+ * The maximum number of times that we display the notification for a network boost via premium
+ * capabilities when {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+ * returns a failure due to user action or timeout.
+ *
+ * An int array with 2 values: {max_notifications_per_day, max_notifications_per_month}.
+ *
+ * The default value is {2, 10}, meaning we display a maximum of 2 network boost notifications
+ * per day and 10 notifications per month.
+ *
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ */
+ public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY =
+ "premium_capability_maximum_notification_count_int_array";
+
+ /**
* The amount of time in milliseconds that the purchase request should be throttled when
* {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
* returns a failure due to the carrier.
@@ -8752,6 +8770,20 @@ public class CarrierConfigManager {
"premium_capability_purchase_url_string";
/**
+ * Whether to allow premium capabilities to be purchased when the device is connected to LTE.
+ * If this is {@code true}, applications can call
+ * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+ * when connected to {@link TelephonyManager#NETWORK_TYPE_LTE} to purchase and use
+ * premium capabilities.
+ * If this is {@code false}, applications can only purchase and use premium capabilities when
+ * conencted to {@link TelephonyManager#NETWORK_TYPE_NR}.
+ *
+ * This is {@code false} by default.
+ */
+ public static final String KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL =
+ "premium_capability_supported_on_lte_bool";
+
+ /**
* IWLAN handover rules that determine whether handover is allowed or disallowed between
* cellular and IWLAN.
*
@@ -9432,15 +9464,18 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
- sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[]{});
+ sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[] {});
sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
+ sDefaults.putIntArray(KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY,
+ new int[] {2, 10});
sDefaults.putLong(
KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
+ sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false);
sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
"source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
+ "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index eb3affcd3322..439eaa69e771 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -54,6 +54,7 @@ import android.os.Looper;
import android.os.ParcelUuid;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.Telephony.SimInfo;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
@@ -4154,4 +4155,79 @@ public class SubscriptionManager {
return "UNKNOWN(" + usageSetting + ")";
}
}
+
+ /**
+ * Set userHandle for a subscription.
+ *
+ * Used to set an association between a subscription and a user on the device so that voice
+ * calling and SMS from that subscription can be associated with that user.
+ * Data services are always shared between users on the device.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @param userHandle the userHandle associated with the subscription.
+ * Pass {@code null} user handle to clear the association.
+ *
+ * @throws IllegalArgumentException if subscription is invalid.
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+ public void setUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
+ if (!isValidSubscriptionId(subscriptionId)) {
+ throw new IllegalArgumentException("[setUserHandle]: Invalid subscriptionId: "
+ + subscriptionId);
+ }
+
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setUserHandle(userHandle, subscriptionId, mContext.getOpPackageName());
+ } else {
+ throw new IllegalStateException("[setUserHandle]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get UserHandle of this subscription.
+ *
+ * Used to get user handle associated with this subscription.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @return userHandle associated with this subscription
+ * or {@code null} if subscription is not associated with any user.
+ *
+ * @throws IllegalArgumentException if subscription is invalid.
+ * @throws SecurityException if the caller doesn't have permissions required.
+ * @throws IllegalStateException if subscription service is not available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+ public @Nullable UserHandle getUserHandle(int subscriptionId) {
+ if (!isValidSubscriptionId(subscriptionId)) {
+ throw new IllegalArgumentException("[getUserHandle]: Invalid subscriptionId: "
+ + subscriptionId);
+ }
+
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ return iSub.getUserHandle(subscriptionId, mContext.getOpPackageName());
+ } else {
+ throw new IllegalStateException("[getUserHandle]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ return null;
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f3d48a849b0f..9ecebf1a28f3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -54,6 +54,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -15626,11 +15627,29 @@ public class TelephonyManager {
public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2;
/**
+ * Allow switching mobile data to the non-default SIM if the non-default SIM has better
+ * availability.
+ *
+ * This is used for temporarily allowing data on the non-default data SIM when on-default SIM
+ * has better availability on DSDS devices, where better availability means strong
+ * signal/connectivity.
+ * If this policy is enabled, data will be temporarily enabled on the non-default data SIM,
+ * including during any voice calls(equivalent to enabling
+ * {@link #MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL}).
+ *
+ * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOBILE_DATA_POLICY_AUTO_DATA_SWITCH = 3;
+
+ /**
* @hide
*/
@IntDef(prefix = { "MOBILE_DATA_POLICY_" }, value = {
MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+ MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface MobileDataPolicy { }
@@ -17115,11 +17134,12 @@ public class TelephonyManager {
}
/**
- * A premium capability boosting the network to allow real-time interactive traffic.
- * Corresponds to NetworkCapabilities#NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC.
+ * A premium capability that boosts the network to allow for real-time interactive traffic
+ * by prioritizing low latency communication.
+ * Corresponds to {@link NetworkCapabilities#NET_CAPABILITY_PRIORITIZE_LATENCY}.
*/
- // TODO(b/245748544): add @link once NET_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC is defined.
- public static final int PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC = 1;
+ public static final int PREMIUM_CAPABILITY_PRIORITIZE_LATENCY =
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
/**
* Purchasable premium capabilities.
@@ -17127,7 +17147,7 @@ public class TelephonyManager {
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "PREMIUM_CAPABILITY_" }, value = {
- PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC})
+ PREMIUM_CAPABILITY_PRIORITIZE_LATENCY})
public @interface PremiumCapability {}
/**
@@ -17139,8 +17159,8 @@ public class TelephonyManager {
*/
public static String convertPremiumCapabilityToString(@PremiumCapability int capability) {
switch (capability) {
- case PREMIUM_CAPABILITY_REALTIME_INTERACTIVE_TRAFFIC:
- return "REALTIME_INTERACTIVE_TRAFFIC";
+ case PREMIUM_CAPABILITY_PRIORITIZE_LATENCY:
+ return "PRIORITIZE_LATENCY";
default:
return "UNKNOWN (" + capability + ")";
}
@@ -17178,11 +17198,18 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1;
/**
- * Purchase premium capability failed because the request is throttled for the amount of time
+ * Purchase premium capability failed because the request is throttled.
+ * If purchasing premium capabilities is throttled, it will be for the amount of time
* specified by {@link CarrierConfigManager
- * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
- * or {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
+ * If displaying the network boost notification is throttled, it will be for the amount of time
+ * specified by {@link CarrierConfigManager
+ * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_INT_ARRAY}.
+ * If a foreground application requests premium capabilities, the network boost notification
+ * will be displayed to the user regardless of the throttled status.
+ * We will show the network boost notification to the user up to the daily and monthly maximum
+ * number of times specified by {@link CarrierConfigManager
+ * #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
* Subsequent attempts will return the same error until the request is no longer throttled
* or throttling conditions change.
*/
@@ -17202,10 +17229,14 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4;
/**
- * Purchase premium capability failed because the user disabled the feature.
- * Subsequent attempts will return the same error until the user re-enables the feature.
+ * Purchase premium capability failed because a foreground application requested the same
+ * capability. The notification for the current application will be dismissed and a new
+ * notification will be displayed to the user for the foreground application.
+ * Subsequent attempts will return
+ * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS} until the foreground
+ * application's request is completed.
*/
- public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 5;
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5;
/**
* Purchase premium capability failed because the user canceled the operation.
@@ -17252,7 +17283,8 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10;
/**
- * Purchase premium capability failed because the telephony service is down or unavailable.
+ * Purchase premium capability failed because the telephony service is unavailable
+ * or there was an error in the phone process.
* Subsequent attempts will return the same error until request conditions are satisfied.
*/
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11;
@@ -17274,6 +17306,14 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13;
/**
+ * Purchase premium capability failed because the request was not made on the default data
+ * subscription, indicated by {@link SubscriptionManager#getDefaultDataSubscriptionId()}.
+ * Subsequent attempts will return the same error until the request is made on the default
+ * data subscription.
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17283,14 +17323,15 @@ public class TelephonyManager {
PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN,
PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR,
PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA})
public @interface PurchasePremiumCapabilityResult {}
/**
@@ -17311,8 +17352,8 @@ public class TelephonyManager {
return "ALREADY_PURCHASED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS:
return "ALREADY_IN_PROGRESS";
- case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED:
- return "USER_DISABLED";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN:
+ return "OVERRIDDEN";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED:
return "USER_CANCELED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED:
@@ -17329,6 +17370,8 @@ public class TelephonyManager {
return "NETWORK_NOT_AVAILABLE";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
return "NETWORK_CONGESTED";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
+ return "NOT_DEFAULT_DATA";
default:
return "UNKNOWN (" + result + ")";
}
@@ -17346,7 +17389,7 @@ public class TelephonyManager {
* @param callback The result of the purchase request.
* One of {@link PurchasePremiumCapabilityResult}.
* @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
- * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid
+ * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
*/
@RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
public void purchasePremiumCapability(@PremiumCapability int capability,
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 917f35bc1b82..0211a7f5c5c5 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -18,6 +18,7 @@ package com.android.internal.telephony;
import android.telephony.SubscriptionInfo;
import android.os.ParcelUuid;
+import android.os.UserHandle;
import com.android.internal.telephony.ISetOpportunisticDataCallback;
interface ISub {
@@ -316,4 +317,28 @@ interface ISub {
* @throws SecurityException if doesn't have MODIFY_PHONE_STATE or Carrier Privileges
*/
int setUsageSetting(int usageSetting, int subId, String callingPackage);
+
+ /**
+ * Set userHandle for this subscription.
+ *
+ * @param userHandle the user handle for this subscription
+ * @param subId the unique SubscriptionInfo index in database
+ * @param callingPackage The package making the IPC.
+ *
+ * @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
+ * @throws IllegalArgumentException if subId is invalid.
+ */
+ int setUserHandle(in UserHandle userHandle, int subId, String callingPackage);
+
+ /**
+ * Get UserHandle for this subscription
+ *
+ * @param subId the unique SubscriptionInfo index in database
+ * @param callingPackage the package making the IPC
+ * @return userHandle associated with this subscription.
+ *
+ * @throws SecurityException if doesn't have SMANAGE_SUBSCRIPTION_USER_ASSOCIATION
+ * @throws IllegalArgumentException if subId is invalid.
+ */
+ UserHandle getUserHandle(int subId, String callingPackage);
}
diff --git a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
index 916551a8ce6d..79a2f1f5f4de 100644
--- a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
+++ b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
@@ -75,6 +75,7 @@ public class MainActivity extends Activity {
String rollbackStatus = "FAILED";
if (rollbackStatusCode == RollbackManager.STATUS_SUCCESS) {
rollbackStatus = "SUCCESS";
+ mTriggerRollbackButton.setClickable(false);
}
makeToast("Status for rollback ID " + rollbackId + " is " + rollbackStatus);
}}, new IntentFilter(ACTION_NAME), Context.RECEIVER_NOT_EXPORTED);