summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java106
-rw-r--r--api/Android.bp1
-rw-r--r--boot/Android.bp4
-rw-r--r--core/api/current.txt17
-rw-r--r--core/api/system-current.txt65
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityManager.java2
-rw-r--r--core/java/android/app/BroadcastOptions.java124
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/app/SystemServiceRegistry.java2
-rw-r--r--core/java/android/app/backup/BackupManager.java15
-rw-r--r--core/java/android/app/backup/BackupManagerMonitorWrapper.java41
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java280
-rw-r--r--core/java/android/app/backup/BackupTransport.java24
-rw-r--r--core/java/android/app/backup/RestoreSession.java14
-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.java19
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl1
-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/Constants.java33
-rw-r--r--core/java/android/credentials/ui/Entry.java37
-rw-r--r--core/java/android/credentials/ui/IntentFactory.java68
-rw-r--r--core/java/android/credentials/ui/ProviderData.java129
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java6
-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.java14
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl5
-rw-r--r--core/java/android/hardware/input/VirtualDpad.java18
-rw-r--r--core/java/android/hardware/input/VirtualInputDevice.java73
-rw-r--r--core/java/android/hardware/input/VirtualKeyboard.java19
-rw-r--r--core/java/android/hardware/input/VirtualMouse.java20
-rw-r--r--core/java/android/hardware/input/VirtualTouchscreen.java21
-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/inputmethodservice/IRemoteInputConnectionInvoker.java4
-rw-r--r--core/java/android/os/Binder.java15
-rw-r--r--core/java/android/os/PowerManagerInternal.java6
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfo.java184
-rw-r--r--core/java/android/service/dreams/DreamActivity.java18
-rw-r--r--core/java/android/service/dreams/DreamService.java12
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java8
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rw-r--r--core/java/android/text/Layout.java12
-rw-r--r--core/java/android/view/HandwritingInitiator.java8
-rw-r--r--core/java/android/view/ImeFocusController.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java36
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java8
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java130
-rw-r--r--core/java/android/widget/TextView.java184
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl10
-rw-r--r--core/java/com/android/internal/inputmethod/EditableInputConnection.java6
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodDebug.java2
-rw-r--r--core/java/com/android/internal/inputmethod/StartInputReason.java6
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java8
-rw-r--r--core/java/com/android/internal/notification/SystemNotificationChannels.java13
-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.xml15
-rw-r--r--core/res/res/drawable-hdpi/ic_notification_ime_default.pngbin755 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_notification_ime_default.pngbin642 -> 0 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_notification_ime_default.pngbin1012 -> 0 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_notification_ime_default.pngbin1183 -> 0 bytes
-rw-r--r--core/res/res/layout/notification_template_header.xml1
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/config_telephony.xml13
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/BroadcastRadioTests/Android.bp25
-rw-r--r--core/tests/BroadcastRadioTests/AndroidManifest.xml2
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java6
-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.java648
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java109
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java131
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java565
-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--graphics/java/android/graphics/BLASTBufferQueue.java2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml17
-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.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java121
-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/windowdecor/WindowDecorationTests.java45
-rw-r--r--media/java/android/media/ImageWriter.java7
-rw-r--r--media/java/android/media/MediaCrypto.java15
-rw-r--r--packages/CarrierDefaultApp/Android.bp1
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml18
-rw-r--r--packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml23
-rw-r--r--packages/CarrierDefaultApp/res/values/strings.xml14
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java122
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java315
-rw-r--r--packages/CredentialManager/res/values/strings.xml6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt75
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt29
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt8
-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/ActivityEmbedding/Android.bp5
-rw-r--r--packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml1
-rw-r--r--packages/SettingsLib/Spa/build.gradle1
-rw-r--r--packages/SettingsLib/Spa/gallery/AndroidManifest.xml20
-rw-r--r--packages/SettingsLib/Spa/gallery/build.gradle4
-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/AndroidManifest.xml5
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt238
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt126
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt40
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt)85
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt176
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt10
-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/MainSwitchPreference.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt6
-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/preference/SliderPreference.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt2
-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/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt)2
-rw-r--r--packages/SettingsLib/Spa/tests/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/tests/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/Spa/tests/build.gradle4
-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/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt9
-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.xml3
-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/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt100
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt1
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt149
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt22
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt208
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt48
-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/plugin/Android.bp1
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt)77
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogLevel.kt)11
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogMessage.kt)25
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt)41
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt)18
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt)54
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt)6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt (renamed from packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt)30
-rw-r--r--packages/SystemUI/plugin/tests/log/LogBufferTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt)54
-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/layout/combined_qs_header.xml4
-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/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/integers.xml7
-rw-r--r--packages/SystemUI/res/values/styles.xml82
-rw-r--r--packages/SystemUI/res/xml/combined_qs_header_scene.xml51
-rw-r--r--packages/SystemUI/res/xml/qqs_header.xml11
-rw-r--r--packages/SystemUI/res/xml/qs_header_new.xml16
-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.kt11
-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/regionsampling/RegionSamplingInstance.kt36
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java380
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java108
-rw-r--r--packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt54
-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/KeyguardInputViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java14
-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/BiometricMessageDeferralLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java6
-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/UdfpsLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt206
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt10
-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/controls/ui/ControlsActivity.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java1
-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/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.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt)15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt4
-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.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt6
-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/QSFragmentDisableFlagsLogger.kt4
-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/external/TileServices.java2
-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/logging/QSLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt123
-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/ActionClickLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt4
-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/HeadsUpCoordinatorLogger.kt5
-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/collection/coordinator/PreparationCoordinatorLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt200
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt4
-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/shared/ConnectivityPipelineLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt12
-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/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt69
-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/KeyguardMessageAreaControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt6
-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/MediaTttLoggerTest.kt4
-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.kt349
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java51
-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/tiles/UserDetailViewAdapterTest.kt6
-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/LargeScreenShadeHeaderControllerCombinedTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java241
-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.java76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt210
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java4
-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/shared/ConnectivityPipelineLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt546
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt131
-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--proto/src/system_messages.proto2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java63
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java11
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java50
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java39
-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.java49
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java29
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java7
-rw-r--r--services/core/java/com/android/server/am/UserController.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java2
-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/MultiBiometricLockoutState.java5
-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/FingerprintAuthenticationClient.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java15
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java19
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java19
-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.java11
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java231
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java86
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java10
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java21
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java14
-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/DexOptHelper.java167
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java33
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java13
-rw-r--r--services/core/java/com/android/server/pm/dex/DexoptOptions.java148
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java9
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java56
-rw-r--r--services/core/java/com/android/server/power/SystemPropertiesWrapper.java15
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java13
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java3
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java10
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java21
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java94
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java204
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java125
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java57
-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/input/BatteryControllerTests.kt146
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java165
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java39
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java33
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java59
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java551
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java40
-rw-r--r--services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java62
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java45
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java63
-rw-r--r--tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml4
-rw-r--r--tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java1
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt6
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt129
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt67
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt57
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt271
529 files changed, 14429 insertions, 5444 deletions
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java
new file mode 100644
index 000000000000..412cb5acbf8b
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializerPerfTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.libcore;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.XmlObjectFactory;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Compares various kinds of method invocation.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class XmlSerializerPerfTest {
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void timeFastSerializer_nonIndent_depth100() throws IOException {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ XmlSerializer serializer = Xml.newFastSerializer();
+ runTest(serializer, 100);
+ }
+ }
+
+ @Test
+ public void timeFastSerializer_indent_depth100() throws IOException {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ XmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ runTest(serializer, 100);
+ }
+ }
+
+ @Test
+ public void timeKXmlSerializer_nonIndent_depth100() throws IOException {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ XmlSerializer serializer = XmlObjectFactory.newXmlSerializer();
+ runTest(serializer, 100);
+ }
+ }
+
+ @Test
+ public void timeKXmlSerializer_indent_depth100() throws IOException {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ XmlSerializer serializer = XmlObjectFactory.newXmlSerializer();
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ runTest(serializer, 100);
+ }
+ }
+
+ private void runTest(XmlSerializer serializer, int depth) throws IOException {
+ File file = File.createTempFile(XmlSerializerPerfTest.class.getSimpleName(), "tmp");
+ try (OutputStream out = new FileOutputStream(file)) {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ writeContent(serializer, depth);
+ serializer.endDocument();
+ }
+ }
+
+ private void writeContent(XmlSerializer serializer, int depth) throws IOException {
+ serializer.startTag(null, "tag");
+ serializer.attribute(null, "attribute", "value1");
+ if (depth > 0) {
+ writeContent(serializer, depth - 1);
+ }
+ serializer.endTag(null, "tag");
+ }
+
+}
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 cc1451266ac8..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";
@@ -41793,6 +41799,8 @@ package android.telephony {
field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
+ field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+ field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
field public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
@@ -44006,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
@@ -44014,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
@@ -45401,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..09b0dcaf98db 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2502,11 +2502,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 +2552,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 +2593,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 {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1e4023eeee18..186e5be92524 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -134,7 +134,7 @@ package android.app {
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 576b572dcc9a..cb7b478d73b4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4401,7 +4401,7 @@ public class ActivityManager {
*/
@TestApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.CREATE_USERS})
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
int displayId) {
if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index c2df8022af0f..cc4650a7df71 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +35,10 @@ import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -57,8 +62,11 @@ 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;
+ private @Nullable String mDeliveryGroupKey;
/**
* Change ID which is invalid.
@@ -161,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.
@@ -190,6 +205,46 @@ public class BroadcastOptions extends ComponentOptions {
private static final String KEY_REMOVE_MATCHING_FILTER =
"android:broadcast.removeMatchingFilter";
+ /**
+ * Corresponds to {@link #setDeliveryGroupPolicy(int)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_POLICY =
+ "android:broadcast.deliveryGroupPolicy";
+
+ /**
+ * Corresponds to {@link #setDeliveryGroupKey(String, String)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_KEY =
+ "android:broadcast.deliveryGroupKey";
+
+ /**
+ * The list of delivery group policies which specify how multiple broadcasts belonging to
+ * the same delivery group has to be handled.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
+ DELIVERY_GROUP_POLICY_ALL,
+ DELIVERY_GROUP_POLICY_MOST_RECENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeliveryGroupPolicy {}
+
+ /**
+ * Delivery group policy that indicates that all the broadcasts in the delivery group
+ * need to be delivered as is.
+ *
+ * @hide
+ */
+ public static final int DELIVERY_GROUP_POLICY_ALL = 0;
+
+ /**
+ * Delivery group policy that indicates that only the most recent broadcast in the delivery
+ * group need to be delivered and the rest can be dropped.
+ *
+ * @hide
+ */
+ public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
+
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -234,8 +289,12 @@ 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,
+ DELIVERY_GROUP_POLICY_ALL);
+ mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
}
/**
@@ -549,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.
@@ -639,6 +719,41 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
+ * the same delivery group has to be handled.
+ *
+ * @hide
+ */
+ public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
+ mDeliveryGroupPolicy = policy;
+ }
+
+ /** @hide */
+ public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
+ return mDeliveryGroupPolicy;
+ }
+
+ /**
+ * Set namespace and key to identify the delivery group that this broadcast belongs to.
+ * If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
+ * used to identify the delivery group.
+ *
+ * @hide
+ */
+ public void setDeliveryGroupKey(@NonNull String namespace, @NonNull String key) {
+ Preconditions.checkArgument(!namespace.contains("/"),
+ "namespace should not contain '/'");
+ Preconditions.checkArgument(!key.contains("/"),
+ "key should not contain '/'");
+ mDeliveryGroupKey = namespace + "/" + key;
+ }
+
+ /** @hide */
+ public String getDeliveryGroupKey() {
+ return mDeliveryGroupKey;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
@@ -658,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);
}
@@ -686,6 +804,12 @@ public class BroadcastOptions extends ComponentOptions {
if (mRemoveMatchingFilter != null) {
b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
}
+ if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
+ b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
+ }
+ if (mDeliveryGroupKey != null) {
+ b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 74eb1c526777..f9ef3cc7e1f6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8467,8 +8467,8 @@ public class Notification implements Parcelable
}
int maxAvatarSize = resources.getDimensionPixelSize(
- isLowRam ? R.dimen.notification_person_icon_max_size
- : R.dimen.notification_person_icon_max_size_low_ram);
+ isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
+ : R.dimen.notification_person_icon_max_size);
if (mUser != null && mUser.getIcon() != null) {
mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
}
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/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 88a7c0f910d3..d2c797255b0d 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -29,7 +29,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -1123,18 +1122,4 @@ public class BackupManager {
});
}
}
-
- private class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub {
- final BackupManagerMonitor mMonitor;
-
- BackupManagerMonitorWrapper(BackupManagerMonitor monitor) {
- mMonitor = monitor;
- }
-
- @Override
- public void onEvent(final Bundle event) throws RemoteException {
- mMonitor.onEvent(event);
- }
- }
-
}
diff --git a/core/java/android/app/backup/BackupManagerMonitorWrapper.java b/core/java/android/app/backup/BackupManagerMonitorWrapper.java
new file mode 100644
index 000000000000..0b189958ef0a
--- /dev/null
+++ b/core/java/android/app/backup/BackupManagerMonitorWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.app.backup;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * Wrapper around {@link BackupManagerMonitor} that helps with IPC between the caller of backup
+ * APIs and the backup service.
+ *
+ * The caller implements {@link BackupManagerMonitor} and passes it into framework APIs that run on
+ * the caller's process. Those framework APIs will then wrap it around this class when doing the
+ * actual IPC.
+ */
+class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub {
+ private final BackupManagerMonitor mMonitor;
+
+ BackupManagerMonitorWrapper(BackupManagerMonitor monitor) {
+ mMonitor = monitor;
+ }
+
+ @Override
+ public void onEvent(final Bundle event) throws RemoteException {
+ mMonitor.onEvent(event);
+ }
+}
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
new file mode 100644
index 000000000000..b789b38c966e
--- /dev/null
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -0,0 +1,280 @@
+/*
+ * 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.app.backup;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+// TODO(b/244436184): Make this @SystemApi
+/**
+ * Class to log B&R stats for each data type that is backed up and restored by the calling app.
+ *
+ * The logger instance is designed to accept a limited number of unique
+ * {link @BackupRestoreDataType} values, as determined by the underlying implementation. Apps are
+ * expected to have a small pre-defined set of data type values they use. Attempts to log too many
+ * unique values will be rejected.
+ *
+ * @hide
+ */
+public class BackupRestoreEventLogger {
+ /**
+ * Max number of unique data types for which an instance of this logger can store info. Attempts
+ * to use more distinct data type values will be rejected.
+ */
+ public static final int DATA_TYPES_ALLOWED = 15;
+
+ /**
+ * Operation types for which this logger can be used.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OperationType.BACKUP,
+ OperationType.RESTORE
+ })
+ @interface OperationType {
+ int BACKUP = 1;
+ int RESTORE = 2;
+ }
+
+ /**
+ * Denotes that the annotated element identifies a data type as required by the logging methods
+ * of {@code BackupRestoreEventLogger}
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BackupRestoreDataType {}
+
+ /**
+ * Denotes that the annotated element identifies an error type as required by the logging
+ * methods of {@code BackupRestoreEventLogger}
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BackupRestoreError {}
+
+ private final int mOperationType;
+
+ /**
+ * @param operationType type of the operation for which logging will be performed. See
+ * {@link OperationType}. Attempts to use logging methods that don't match
+ * the specified operation type will be rejected (e.g. use backup methods
+ * for a restore logger and vice versa).
+ */
+ public BackupRestoreEventLogger(@OperationType int operationType) {
+ mOperationType = operationType;
+ }
+
+ /**
+ * Report progress during a backup operation. Call this method for each distinct data type that
+ * your {@code BackupAgent} implementation handles for any items of that type that have been
+ * successfully backed up. Repeated calls to this method with the same {@code dataType} will
+ * increase the total count of items associated with this data type by {@code count}.
+ *
+ * This method should be called from a {@link BackupAgent} implementation during an ongoing
+ * backup operation.
+ *
+ * @param dataType the type of data being backed.
+ * @param count number of items of the given type that have been successfully backed up.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+ return true;
+ }
+
+ /**
+ * Report errors during a backup operation. Call this method whenever items of a certain data
+ * type failed to back up. Repeated calls to this method with the same {@code dataType} /
+ * {@code error} will increase the total count of items associated with this data type / error
+ * by {@code count}.
+ *
+ * This method should be called from a {@link BackupAgent} implementation during an ongoing
+ * backup operation.
+ *
+ * @param dataType the type of data being backed.
+ * @param count number of items of the given type that have failed to back up.
+ * @param error optional, the error that has caused the failure.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ @Nullable @BackupRestoreError String error) {
+ return true;
+ }
+
+ /**
+ * Report metadata associated with a data type that is currently being backed up, e.g. name of
+ * the selected wallpaper file / package. Repeated calls to this method with the same {@code
+ * dataType} will overwrite the previously supplied {@code metaData} value.
+ *
+ * The logger does not store or transmit the provided metadata value. Instead, it’s replaced
+ * with the SHA-256 hash of the provided string.
+ *
+ * This method should be called from a {@link BackupAgent} implementation during an ongoing
+ * backup operation.
+ *
+ * @param dataType the type of data being backed up.
+ * @param metaData the metadata associated with the data type.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+ @NonNull String metaData) {
+ return true;
+ }
+
+ /**
+ * Report progress during a restore operation. Call this method for each distinct data type that
+ * your {@code BackupAgent} implementation handles if any items of that type have been
+ * successfully restored. Repeated calls to this method with the same {@code dataType} will
+ * increase the total count of items associated with this data type by {@code count}.
+ *
+ * This method should either be called from a {@link BackupAgent} implementation during an
+ * ongoing restore operation or during any delayed restore actions the package had scheduled
+ * earlier (e.g. complete the restore once a certain dependency becomes available on the
+ * device).
+ *
+ * @param dataType the type of data being restored.
+ * @param count number of items of the given type that have been successfully restored.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+ return true;
+ }
+
+ /**
+ * Report errors during a restore operation. Call this method whenever items of a certain data
+ * type failed to restore. Repeated calls to this method with the same {@code dataType} /
+ * {@code error} will increase the total count of items associated with this data type / error
+ * by {@code count}.
+ *
+ * This method should either be called from a {@link BackupAgent} implementation during an
+ * ongoing restore operation or during any delayed restore actions the package had scheduled
+ * earlier (e.g. complete the restore once a certain dependency becomes available on the
+ * device).
+ *
+ * @param dataType the type of data being restored.
+ * @param count number of items of the given type that have failed to restore.
+ * @param error optional, the error that has caused the failure.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ @Nullable @BackupRestoreError String error) {
+ return true;
+ }
+
+ /**
+ * Report metadata associated with a data type that is currently being restored, e.g. name of
+ * the selected wallpaper file / package. Repeated calls to this method with the same
+ * {@code dataType} will overwrite the previously supplied {@code metaData} value.
+ *
+ * The logger does not store or transmit the provided metadata value. Instead, it’s replaced
+ * with the SHA-256 hash of the provided string.
+ *
+ * This method should either be called from a {@link BackupAgent} implementation during an
+ * ongoing restore operation or during any delayed restore actions the package had scheduled
+ * earlier (e.g. complete the restore once a certain dependency becomes available on the
+ * device).
+ *
+ * @param dataType the type of data being restored.
+ * @param metadata the metadata associated with the data type.
+ *
+ * @return boolean, indicating whether the log has been accepted.
+ */
+ public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+ @NonNull String metadata) {
+ return true;
+ }
+
+ /**
+ * Get the contents of this logger. This method should only be used by B&R code in Android
+ * Framework.
+ *
+ * @hide
+ */
+ public List<DataTypeResult> getLoggingResults() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get the operation type for which this logger was created. This method should only be used
+ * by B&R code in Android Framework.
+ *
+ * @hide
+ */
+ public @OperationType int getOperationType() {
+ return mOperationType;
+ }
+
+ /**
+ * Encapsulate logging results for a single data type.
+ */
+ public static class DataTypeResult {
+ @BackupRestoreDataType
+ private final String mDataType;
+ private final int mSuccessCount;
+ private final Map<String, Integer> mErrors;
+ private final byte[] mMetadataHash;
+
+ public DataTypeResult(String dataType, int successCount,
+ Map<String, Integer> errors, byte[] metadataHash) {
+ mDataType = dataType;
+ mSuccessCount = successCount;
+ mErrors = errors;
+ mMetadataHash = metadataHash;
+ }
+
+ @NonNull
+ @BackupRestoreDataType
+ public String getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * @return number of items of the given data type that have been successfully backed up or
+ * restored.
+ */
+ public int getSuccessCount() {
+ return mSuccessCount;
+ }
+
+ /**
+ * @return mapping of {@link BackupRestoreError} to the count of items that are affected by
+ * the error.
+ */
+ @NonNull
+ public Map<String, Integer> getErrors() {
+ return mErrors;
+ }
+
+ /**
+ * @return SHA-256 hash of the metadata or {@code null} of no metadata has been logged for
+ * this data type.
+ */
+ @Nullable
+ public byte[] getMetadataHash() {
+ return mMetadataHash;
+ }
+ }
+}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index f6de72b43de6..90e9df47bc4b 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -656,6 +656,20 @@ public class BackupTransport {
}
/**
+ * Ask the transport for a {@link IBackupManagerMonitor} instance which will be used by the
+ * framework to report logging events back to the transport.
+ *
+ * <p>Backups requested from outside the framework may pass in a monitor with the request,
+ * however backups initiated by the framework will call this method to retrieve one.
+ *
+ * @hide
+ */
+ @Nullable
+ public BackupManagerMonitor getBackupManagerMonitor() {
+ return null;
+ }
+
+ /**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -952,5 +966,15 @@ public class BackupTransport {
callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR);
}
}
+
+ @Override
+ public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture) {
+ try {
+ BackupManagerMonitor result = BackupTransport.this.getBackupManagerMonitor();
+ resultFuture.complete(new BackupManagerMonitorWrapper(result));
+ } catch (RuntimeException e) {
+ resultFuture.cancel(/* mayInterruptIfRunning */ true);
+ }
+ }
}
}
diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java
index 933670415f2e..fe68ec193642 100644
--- a/core/java/android/app/backup/RestoreSession.java
+++ b/core/java/android/app/backup/RestoreSession.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -393,17 +392,4 @@ public class RestoreSession {
mHandler.obtainMessage(MSG_RESTORE_FINISHED, error, 0));
}
}
-
- private class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub {
- final BackupManagerMonitor mMonitor;
-
- BackupManagerMonitorWrapper(BackupManagerMonitor monitor) {
- mMonitor = monitor;
- }
-
- @Override
- public void onEvent(final Bundle event) throws RemoteException {
- mMonitor.onEvent(event);
- }
- }
}
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 cc303fb1f413..24e47bf9e47c 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -418,14 +418,7 @@ public class AppWidgetHost {
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
view.setInteractionHandler(mInteractionHandler);
view.setAppWidget(appWidgetId, appWidget);
- addListener(appWidgetId, view);
- RemoteViews views;
- try {
- views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
- } catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- view.updateAppWidget(views);
+ setListener(appWidgetId, view);
return view;
}
@@ -513,13 +506,19 @@ public class AppWidgetHost {
* The AppWidgetHost retains a pointer to the newly-created listener.
* @param appWidgetId The ID of the app widget for which to add the listener
* @param listener The listener interface that deals with actions towards the widget view
- *
* @hide
*/
- public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
+ public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
synchronized (mListeners) {
mListeners.put(appWidgetId, listener);
}
+ RemoteViews views = null;
+ try {
+ views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ listener.updateAppWidget(views);
}
/**
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index e7f191665302..295d69d4b27d 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -88,6 +88,7 @@ interface IVirtualDevice {
IBinder token,
in Point screenSize);
void unregisterInputDevice(IBinder token);
+ int getInputDeviceId(IBinder token);
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
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/Constants.java b/core/java/android/credentials/ui/Constants.java
new file mode 100644
index 000000000000..aeeede744188
--- /dev/null
+++ b/core/java/android/credentials/ui/Constants.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Constants for the ui protocol that doesn't fit into other individual data structures.
+ *
+ * @hide
+ */
+public class Constants {
+
+ /**
+ * The intent extra key for the {@code ResultReceiver} object when launching the UX
+ * activities.
+ */
+ 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
new file mode 100644
index 000000000000..9a038d137434
--- /dev/null
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+import java.util.ArrayList;
+
+/**
+ * Helpers for generating the intents and related extras parameters to launch the UI activities.
+ *
+ * @hide
+ */
+public class IntentFactory {
+ /** Generate a new launch intent to the . */
+ public static Intent newIntent(RequestInfo requestInfo,
+ 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";
+ intent.setComponent(ComponentName.unflattenFromString(activityName));
+
+ intent.putParcelableArrayListExtra(
+ ProviderData.EXTRA_PROVIDER_DATA_LIST, providerDataList);
+ intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
+ intent.putExtra(Constants.EXTRA_RESULT_RECEIVER,
+ toIpcFriendlyResultReceiver(resultReceiver));
+
+ return intent;
+ }
+
+ /**
+ * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+ * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+ * unmarshall.
+ */
+ private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
+ T resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
+ }
+
+ private IntentFactory() {}
+}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 38bd4e57b758..35e12fa43b28 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -16,8 +16,10 @@
package android.credentials.ui;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
@@ -43,30 +45,49 @@ public class ProviderData implements Parcelable {
@NonNull
private final String mProviderId;
@NonNull
+ private final String mProviderDisplayName;
+ @NonNull
+ private final Icon mIcon;
+ @NonNull
private final List<Entry> mCredentialEntries;
@NonNull
private final List<Entry> mActionChips;
@Nullable
private final Entry mAuthenticationEntry;
+ private final @CurrentTimeMillisLong long mLastUsedTimeMillis;
+
public ProviderData(
- @NonNull String providerId,
- @NonNull List<Entry> credentialEntries,
- @NonNull List<Entry> actionChips,
- @Nullable Entry authenticationEntry) {
+ @NonNull String providerId, @NonNull String providerDisplayName,
+ @NonNull Icon icon, @NonNull List<Entry> credentialEntries,
+ @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
+ @CurrentTimeMillisLong long lastUsedTimeMillis) {
mProviderId = providerId;
+ mProviderDisplayName = providerDisplayName;
+ mIcon = icon;
mCredentialEntries = credentialEntries;
mActionChips = actionChips;
mAuthenticationEntry = authenticationEntry;
+ mLastUsedTimeMillis = lastUsedTimeMillis;
}
- /** Returns the provider package name. */
+ /** Returns the unique provider id. */
@NonNull
public String getProviderId() {
return mProviderId;
}
@NonNull
+ public String getProviderDisplayName() {
+ return mProviderDisplayName;
+ }
+
+ @NonNull
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ @NonNull
public List<Entry> getCredentialEntries() {
return mCredentialEntries;
}
@@ -81,11 +102,24 @@ public class ProviderData implements Parcelable {
return mAuthenticationEntry;
}
+ /** Returns the time when the provider was last used. */
+ public @CurrentTimeMillisLong long getLastUsedTimeMillis() {
+ return mLastUsedTimeMillis;
+ }
+
protected ProviderData(@NonNull Parcel in) {
String providerId = in.readString8();
mProviderId = providerId;
AnnotationValidations.validate(NonNull.class, null, mProviderId);
+ String providerDisplayName = in.readString8();
+ mProviderDisplayName = providerDisplayName;
+ AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName);
+
+ Icon icon = in.readTypedObject(Icon.CREATOR);
+ mIcon = icon;
+ AnnotationValidations.validate(NonNull.class, null, mIcon);
+
List<Entry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, Entry.CREATOR);
mCredentialEntries = credentialEntries;
@@ -98,14 +132,20 @@ public class ProviderData implements Parcelable {
Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
mAuthenticationEntry = authenticationEntry;
+
+ long lastUsedTimeMillis = in.readLong();
+ mLastUsedTimeMillis = lastUsedTimeMillis;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mProviderId);
+ dest.writeString8(mProviderDisplayName);
+ dest.writeTypedObject(mIcon, flags);
dest.writeTypedList(mCredentialEntries);
dest.writeTypedList(mActionChips);
dest.writeTypedObject(mAuthenticationEntry, flags);
+ dest.writeLong(mLastUsedTimeMillis);
}
@Override
@@ -124,4 +164,83 @@ public class ProviderData implements Parcelable {
return new ProviderData[size];
}
};
+
+ /**
+ * Builder for {@link ProviderData}.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private @NonNull String mProviderId;
+ private @NonNull String mProviderDisplayName;
+ private @NonNull Icon mIcon;
+ private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
+ private @NonNull List<Entry> mActionChips = new ArrayList<>();
+ private @Nullable Entry mAuthenticationEntry = null;
+ private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L;
+
+ /** Constructor with required properties. */
+ public Builder(@NonNull String providerId, @NonNull String providerDisplayName,
+ @NonNull Icon icon) {
+ mProviderId = providerId;
+ mProviderDisplayName = providerDisplayName;
+ mIcon = icon;
+ }
+
+ /** Sets the unique provider id. */
+ @NonNull
+ public Builder setProviderId(@NonNull String providerId) {
+ mProviderId = providerId;
+ return this;
+ }
+
+ /** Sets the provider display name to be displayed to the user. */
+ @NonNull
+ public Builder setProviderDisplayName(@NonNull String providerDisplayName) {
+ mProviderDisplayName = providerDisplayName;
+ return this;
+ }
+
+ /** Sets the provider icon to be displayed to the user. */
+ @NonNull
+ public Builder setIcon(@NonNull Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /** Sets the list of save / get credential entries to be displayed to the user. */
+ @NonNull
+ public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+ mCredentialEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the list of action chips to be displayed to the user. */
+ @NonNull
+ public Builder setActionChips(@NonNull List<Entry> actionChips) {
+ mActionChips = actionChips;
+ return this;
+ }
+
+ /** Sets the authentication entry to be displayed to the user. */
+ @NonNull
+ public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) {
+ mAuthenticationEntry = authenticationEntry;
+ return this;
+ }
+
+ /** Sets the time when the provider was last used. */
+ @NonNull
+ public Builder setLastUsedTimeMillis(@CurrentTimeMillisLong long lastUsedTimeMillis) {
+ mLastUsedTimeMillis = lastUsedTimeMillis;
+ return this;
+ }
+
+ /** Builds a {@link ProviderData}. */
+ @NonNull
+ public ProviderData build() {
+ return new ProviderData(mProviderId, mProviderDisplayName, mIcon, mCredentialEntries,
+ mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
+ }
+ }
}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 5de6d73945eb..eddb519051a9 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -36,12 +36,6 @@ public class RequestInfo implements Parcelable {
*/
public static final @NonNull String EXTRA_REQUEST_INFO =
"android.credentials.ui.extra.REQUEST_INFO";
- /**
- * The intent extra key for the {@code ResultReceiver} object when launching the UX
- * activities.
- */
- public static final @NonNull String EXTRA_RESULT_RECEIVER =
- "android.credentials.ui.extra.RESULT_RECEIVER";
/** Type value for an executeGetCredential request. */
public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
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 715361eafde9..5403f089b308 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -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/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index d7cda9ec33cf..4d61553ccb52 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -24,7 +24,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.view.KeyEvent;
-import java.io.Closeable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -39,7 +38,7 @@ import java.util.Set;
* @hide
*/
@SystemApi
-public class VirtualDpad implements Closeable {
+public class VirtualDpad extends VirtualInputDevice {
private final Set<Integer> mSupportedKeyCodes =
Collections.unmodifiableSet(
@@ -50,23 +49,10 @@ public class VirtualDpad implements Closeable {
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_DPAD_CENTER)));
- private final IVirtualDevice mVirtualDevice;
- private final IBinder mToken;
/** @hide */
public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) {
- mVirtualDevice = virtualDevice;
- mToken = token;
- }
-
- @Override
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void close() {
- try {
- mVirtualDevice.unregisterInputDevice(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ super(virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java
new file mode 100644
index 000000000000..772ba8e36c5e
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualInputDevice.java
@@ -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 android.hardware.input;
+
+import android.annotation.RequiresPermission;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+
+/**
+ * The base class for all virtual input devices such as VirtualKeyboard, VirtualMouse.
+ * This implements the shared functionality such as closing the device and keeping track of
+ * identifiers.
+ *
+ * @hide
+ */
+abstract class VirtualInputDevice implements Closeable {
+
+ /**
+ * The virtual device to which this VirtualInputDevice belongs to.
+ */
+ protected final IVirtualDevice mVirtualDevice;
+
+ /**
+ * The token used to uniquely identify the virtual input device.
+ */
+ protected final IBinder mToken;
+
+ /** @hide */
+ VirtualInputDevice(
+ IVirtualDevice virtualDevice, IBinder token) {
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ /**
+ * @return The device id of this device.
+ * @hide
+ */
+ public int getInputDeviceId() {
+ try {
+ return mVirtualDevice.getInputDeviceId(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void close() {
+ try {
+ mVirtualDevice.unregisterInputDevice(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index 901401fea32c..e569dbf6b6b6 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -24,8 +24,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.view.KeyEvent;
-import java.io.Closeable;
-
/**
* A virtual keyboard representing a key input mechanism on a remote device, such as a built-in
* keyboard on a laptop, a software keyboard on a tablet, or a keypad on a TV remote control.
@@ -36,26 +34,13 @@ import java.io.Closeable;
* @hide
*/
@SystemApi
-public class VirtualKeyboard implements Closeable {
+public class VirtualKeyboard extends VirtualInputDevice {
private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER;
- private final IVirtualDevice mVirtualDevice;
- private final IBinder mToken;
/** @hide */
public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) {
- mVirtualDevice = virtualDevice;
- mToken = token;
- }
-
- @Override
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void close() {
- try {
- mVirtualDevice.unregisterInputDevice(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ super(virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
index 6e2b56a2b5bc..7eba2b8bfdf0 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -25,8 +25,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.view.MotionEvent;
-import java.io.Closeable;
-
/**
* A virtual mouse representing a relative input mechanism on a remote device, such as a mouse or
* trackpad.
@@ -37,25 +35,11 @@ import java.io.Closeable;
* @hide
*/
@SystemApi
-public class VirtualMouse implements Closeable {
-
- private final IVirtualDevice mVirtualDevice;
- private final IBinder mToken;
+public class VirtualMouse extends VirtualInputDevice {
/** @hide */
public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) {
- mVirtualDevice = virtualDevice;
- mToken = token;
- }
-
- @Override
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void close() {
- try {
- mVirtualDevice.unregisterInputDevice(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ super(virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
index c8d602acaff6..0d07753b9b60 100644
--- a/core/java/android/hardware/input/VirtualTouchscreen.java
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -23,8 +23,6 @@ import android.companion.virtual.IVirtualDevice;
import android.os.IBinder;
import android.os.RemoteException;
-import java.io.Closeable;
-
/**
* A virtual touchscreen representing a touch-based display input mechanism on a remote device.
*
@@ -34,25 +32,10 @@ import java.io.Closeable;
* @hide
*/
@SystemApi
-public class VirtualTouchscreen implements Closeable {
-
- private final IVirtualDevice mVirtualDevice;
- private final IBinder mToken;
-
+public class VirtualTouchscreen extends VirtualInputDevice {
/** @hide */
public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) {
- mVirtualDevice = virtualDevice;
- mToken = token;
- }
-
- @Override
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void close() {
- try {
- mVirtualDevice.unregisterInputDevice(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ super(virtualDevice, token);
}
/**
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/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 891da2466bff..4f09beec81dd 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -777,7 +777,7 @@ final class IRemoteInputConnectionInvoker {
}
/**
- * Invokes {@link IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
+ * Invokes {@code IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
* CharSequence, TextAttribute)}.
*
* @param start the character index where the replacement should start.
@@ -788,6 +788,8 @@ final class IRemoteInputConnectionInvoker {
* that this means you can't position the cursor within the text.
* @param text the text to replace. This may include styles.
* @param textAttribute The extra information about the text. This value may be null.
+ * @return {@code true} if the invocation is completed without {@link RemoteException}, {@code
+ * false} otherwise.
*/
@AnyThread
public boolean replaceText(
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4df013949de5..d3a6323230a5 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1246,8 +1246,21 @@ public class Binder implements IBinder {
// If the call was {@link IBinder#FLAG_ONEWAY} then these exceptions
// disappear into the ether.
final boolean tagEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL);
+ final boolean hasFullyQualifiedName = getMaxTransactionId() > 0;
final String transactionTraceName;
- if (tagEnabled) {
+
+ if (tagEnabled && hasFullyQualifiedName) {
+ // If tracing enabled and we have a fully qualified name, fetch the name
+ transactionTraceName = getTransactionTraceName(code);
+ } else if (tagEnabled && isStackTrackingEnabled()) {
+ // If tracing is enabled and we *don't* have a fully qualified name, fetch the
+ // 'best effort' name only for stack tracking. This works around noticeable perf impact
+ // on low latency binder calls (<100us). The tracing call itself is between (1-10us) and
+ // the perf impact can be quite noticeable while benchmarking such binder calls.
+ // The primary culprits are ContentProviders and Cursors which convenienty don't
+ // autogenerate their AIDL and hence will not have a fully qualified name.
+ //
+ // TODO(b/253426478): Relax this constraint after a more robust fix
transactionTraceName = getTransactionTraceName(code);
} else {
transactionTraceName = null;
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index f62cc879cce3..8afd6de235a0 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -341,4 +341,10 @@ public abstract class PowerManagerInternal {
* device is not awake.
*/
public abstract void nap(long eventTime, boolean allowWake);
+
+ /**
+ * Returns true if ambient display is suppressed by any app with any token. This method will
+ * return false if ambient display is not available.
+ */
+ public abstract boolean isAmbientDisplaySuppressed();
}
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/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/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index f6a7c8eb8c4b..a2fa1392b079 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -44,6 +44,8 @@ import android.text.TextUtils;
public class DreamActivity extends Activity {
static final String EXTRA_CALLBACK = "binder";
static final String EXTRA_DREAM_TITLE = "title";
+ @Nullable
+ private DreamService.DreamActivityCallbacks mCallback;
public DreamActivity() {}
@@ -57,11 +59,19 @@ public class DreamActivity extends Activity {
}
final Bundle extras = getIntent().getExtras();
- final DreamService.DreamActivityCallback callback =
- (DreamService.DreamActivityCallback) extras.getBinder(EXTRA_CALLBACK);
+ mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
- if (callback != null) {
- callback.onActivityCreated(this);
+ if (mCallback != null) {
+ mCallback.onActivityCreated(this);
}
}
+
+ @Override
+ public void onDestroy() {
+ if (mCallback != null) {
+ mCallback.onActivityDestroyed();
+ }
+
+ super.onDestroy();
+ }
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3c1fef02f9ba..cb0dce91589e 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1295,7 +1295,7 @@ public class DreamService extends Service implements Window.Callback {
Intent i = new Intent(this, DreamActivity.class);
i.setPackage(getApplicationContext().getPackageName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallback(mDreamToken));
+ i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
@@ -1488,10 +1488,10 @@ public class DreamService extends Service implements Window.Callback {
}
/** @hide */
- final class DreamActivityCallback extends Binder {
+ final class DreamActivityCallbacks extends Binder {
private final IBinder mActivityDreamToken;
- DreamActivityCallback(IBinder token) {
+ DreamActivityCallbacks(IBinder token) {
mActivityDreamToken = token;
}
@@ -1516,6 +1516,12 @@ public class DreamService extends Service implements Window.Callback {
mActivity = activity;
onWindowCreated(activity.getWindow());
}
+
+ // If DreamActivity is destroyed, wake up from Dream.
+ void onActivityDestroyed() {
+ mActivity = null;
+ onDestroy();
+ }
}
/**
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/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index b559161ae0ad..a59d429b24e6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -284,7 +284,6 @@ public abstract class WallpaperService extends Service {
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
- private @Surface.Rotation int mDisplayInstallOrientation;
private float mWallpaperDimAmount = 0.05f;
private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
private float mDefaultDimAmount = mWallpaperDimAmount;
@@ -1159,7 +1158,7 @@ public abstract class WallpaperService extends Service {
mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
final int transformHint = SurfaceControl.rotationToBufferTransform(
- (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+ (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
mSurfaceControl.setTransformHint(transformHint);
WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
@@ -1420,7 +1419,6 @@ public abstract class WallpaperService extends Service {
mWallpaperDimAmount = mDefaultDimAmount;
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
mDisplayState = mDisplay.getState();
- mDisplayInstallOrientation = mDisplay.getInstallOrientation();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
onCreate(mSurfaceHolder);
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/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a1ece9298e7f..a0a07b30662f 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -162,7 +162,13 @@ public class HandwritingInitiator {
if (candidateView == getConnectedView()) {
startHandwriting(candidateView);
} else {
- candidateView.requestFocus();
+ if (candidateView.getRevealOnFocusHint()) {
+ candidateView.setRevealOnFocusHint(false);
+ candidateView.requestFocus();
+ candidateView.setRevealOnFocusHint(true);
+ } else {
+ candidateView.requestFocus();
+ }
}
}
}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 4de7c4fdd513..43828d58afc4 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -108,10 +108,11 @@ public final class ImeFocusController {
}
/**
- * @see InputMethodManager#checkFocus()
+ * @see ViewRootImpl#dispatchCheckFocus()
*/
- public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
- return getImmDelegate().checkFocus(forceNewFocus, startInput, mViewRootImpl);
+ @UiThread
+ void onScheduledCheckFocus() {
+ getImmDelegate().onScheduledCheckFocus(mViewRootImpl);
}
@UiThread
@@ -163,7 +164,7 @@ public final class ImeFocusController {
void onPostWindowGainedFocus(View viewForWindowFocus,
@NonNull WindowManager.LayoutParams windowAttribute);
void onViewFocusChanged(@NonNull View view, boolean hasFocus);
- boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl);
+ void onScheduledCheckFocus(@NonNull ViewRootImpl viewRootImpl);
void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl);
void onWindowDismissed(ViewRootImpl viewRootImpl);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 58c81260076b..efda257aed27 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -428,8 +428,6 @@ public final class ViewRootImpl implements ViewParent,
final DisplayManager mDisplayManager;
final String mBasePackageName;
- private @Surface.Rotation int mDisplayInstallOrientation;
-
final int[] mTmpLocation = new int[2];
final TypedValue mTmpValue = new TypedValue();
@@ -890,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.
}
}
};
@@ -1134,7 +1130,6 @@ public final class ViewRootImpl implements ViewParent,
if (mView == null) {
mView = view;
- mDisplayInstallOrientation = mDisplay.getInstallOrientation();
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
@@ -1905,7 +1900,6 @@ public final class ViewRootImpl implements ViewParent,
updateInternalDisplay(displayId, mView.getResources());
mImeFocusController.onMovedToDisplay();
mAttachInfo.mDisplayState = mDisplay.getState();
- mDisplayInstallOrientation = mDisplay.getInstallOrientation();
// Internal state updated, now notify the view hierarchy.
mView.dispatchMovedToDisplay(mDisplay, config);
}
@@ -5718,7 +5712,7 @@ public final class ViewRootImpl implements ViewParent,
enqueueInputEvent(event, null, 0, true);
} break;
case MSG_CHECK_FOCUS: {
- getImeFocusController().checkFocus(false, true);
+ getImeFocusController().onScheduledCheckFocus();
} break;
case MSG_CLOSE_SYSTEM_DIALOGS: {
if (mView != null) {
@@ -8235,7 +8229,7 @@ public final class ViewRootImpl implements ViewParent,
}
final int transformHint = SurfaceControl.rotationToBufferTransform(
- (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+ (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
@@ -8260,7 +8254,7 @@ public final class ViewRootImpl implements ViewParent,
}
mLastTransformHint = transformHint;
-
+
mSurfaceControl.setTransformHint(transformHint);
if (mAttachInfo.mContentCaptureManager != null) {
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 4a79ba62de69..febdac26a0f2 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -595,6 +595,10 @@ public class EditorInfo implements InputType, Parcelable {
== HandwritingGesture.GESTURE_TYPE_SELECT) {
list.add(SelectGesture.class);
}
+ if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE)
+ == HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) {
+ list.add(SelectRangeGesture.class);
+ }
if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT)
== HandwritingGesture.GESTURE_TYPE_INSERT) {
list.add(InsertGesture.class);
@@ -603,6 +607,10 @@ public class EditorInfo implements InputType, Parcelable {
== HandwritingGesture.GESTURE_TYPE_DELETE) {
list.add(DeleteGesture.class);
}
+ if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE)
+ == HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) {
+ list.add(DeleteRangeGesture.class);
+ }
if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE)
== HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE) {
list.add(RemoveSpaceGesture.class);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 18b3e212d6f4..574d0356ae42 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,39 +765,37 @@ 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(
"InputMethodManager.DelegateImpl#startInputAsyncOnWindowFocusGain",
InputMethodManager.this, null /* icProto */);
- final ImeFocusController controller = getFocusController();
- if (controller == null) {
- return;
- }
-
+ boolean checkFocusResult;
synchronized (mH) {
+ if (mCurRootView == null) {
+ return;
+ }
if (mRestartOnNextWindowFocus) {
if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus as true");
mRestartOnNextWindowFocus = false;
- forceNewFocus = true;
+ forceFocus = true;
}
+ checkFocusResult = checkFocusInternalLocked(forceFocus, mCurRootView);
}
- if (controller.checkFocus(forceNewFocus, false)) {
+ if (checkFocusResult) {
// We need to restart input on the current focus view. This
// should be done in conjunction with telling the system service
// 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,
@@ -825,9 +825,15 @@ public final class InputMethodManager {
}
@Override
- public boolean checkFocus(boolean forceNewFocus, boolean startInput,
- ViewRootImpl viewRootImpl) {
- return checkFocusInternal(forceNewFocus, startInput, viewRootImpl);
+ public void onScheduledCheckFocus(ViewRootImpl viewRootImpl) {
+ synchronized (mH) {
+ if (!checkFocusInternalLocked(false, viewRootImpl)) {
+ return;
+ }
+ }
+ startInputOnWindowFocusGainInternal(StartInputReason.SCHEDULED_CHECK_FOCUS,
+ null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */,
+ 0 /* windowFlags */);
}
@Override
@@ -937,15 +943,6 @@ public final class InputMethodManager {
return mCurRootView != null ? mNextServedView : null;
}
- private ImeFocusController getFocusController() {
- synchronized (mH) {
- if (mCurRootView != null) {
- return mCurRootView.getImeFocusController();
- }
- return null;
- }
- }
-
/**
* Returns {@code true} when the given view has been served by Input Method.
*/
@@ -1128,8 +1125,7 @@ public final class InputMethodManager {
if (mCurRootView == null) {
return;
}
- if (!mCurRootView.getImeFocusController().checkFocus(
- mRestartOnNextWindowFocus, false)) {
+ if (!checkFocusInternalLocked(mRestartOnNextWindowFocus, mCurRootView)) {
return;
}
final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
@@ -2349,8 +2345,7 @@ public final class InputMethodManager {
}
/**
- * Called from {@link #checkFocusInternal(boolean, boolean, ViewRootImpl)},
- * {@link #restartInput(View)}, {@link #MSG_BIND} or {@link #MSG_UNBIND}.
+ * Starts an input connection from the served view that gains the window focus.
* Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
* background thread may blocked by other methods which already inside {@code mH} lock.
*/
@@ -2658,52 +2653,53 @@ public final class InputMethodManager {
}
/**
- * Check the next served view from {@link ImeFocusController} if needs to start input.
* Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
* background thread may blocked by other methods which already inside {@code mH} lock.
* @hide
*/
@UnsupportedAppUsage
public void checkFocus() {
- final ImeFocusController controller = getFocusController();
- if (controller != null) {
- controller.checkFocus(false /* forceNewFocus */, true /* startInput */);
- }
- }
-
- private boolean checkFocusInternal(boolean forceNewFocus, boolean startInput,
- ViewRootImpl viewRootImpl) {
synchronized (mH) {
- if (mCurRootView != viewRootImpl) {
- return false;
- }
- if (mServedView == mNextServedView && !forceNewFocus) {
- return false;
- }
- if (DEBUG) {
- Log.v(TAG, "checkFocus: view=" + mServedView
- + " next=" + mNextServedView
- + " force=" + forceNewFocus
- + " package="
- + (mServedView != null ? mServedView.getContext().getPackageName()
- : "<none>"));
- }
- // Close the connection when no next served view coming.
- if (mNextServedView == null) {
- finishInputLocked();
- closeCurrentInput();
- return false;
+ if (mCurRootView == null) {
+ return;
}
- mServedView = mNextServedView;
- if (mServedInputConnection != null) {
- mServedInputConnection.finishComposingTextFromImm();
+ if (!checkFocusInternalLocked(false /* forceNewFocus */, mCurRootView)) {
+ return;
}
}
+ startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
+ null /* focusedView */,
+ 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
+ }
- if (startInput) {
- startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
- null /* focusedView */,
- 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
+ /**
+ * Check the next served view if needs to start input.
+ */
+ @GuardedBy("mH")
+ private boolean checkFocusInternalLocked(boolean forceNewFocus, ViewRootImpl viewRootImpl) {
+ if (mCurRootView != viewRootImpl) {
+ return false;
+ }
+ if (mServedView == mNextServedView && !forceNewFocus) {
+ return false;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "checkFocus: view=" + mServedView
+ + " next=" + mNextServedView
+ + " force=" + forceNewFocus
+ + " package="
+ + (mServedView != null ? mServedView.getContext().getPackageName()
+ : "<none>"));
+ }
+ // Close the connection when no next served view coming.
+ if (mNextServedView == null) {
+ finishInputLocked();
+ closeCurrentInput();
+ return false;
+ }
+ mServedView = mNextServedView;
+ if (mServedInputConnection != null) {
+ mServedInputConnection.finishComposingTextFromImm();
}
return true;
}
@@ -3996,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/widget/TextView.java b/core/java/android/widget/TextView.java
index a2e9faa81eeb..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;
@@ -189,6 +188,7 @@ import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.DeleteGesture;
+import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -199,6 +199,7 @@ import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
+import android.view.inputmethod.SelectRangeGesture;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
@@ -9096,7 +9097,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
gestures.add(SelectGesture.class);
+ gestures.add(SelectRangeGesture.class);
gestures.add(DeleteGesture.class);
+ gestures.add(DeleteRangeGesture.class);
gestures.add(InsertGesture.class);
gestures.add(RemoveSpaceGesture.class);
gestures.add(JoinOrSplitGesture.class);
@@ -9313,82 +9316,145 @@ 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) {
+ int[] startRange = getRangeForRect(
+ convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
+ gesture.getGranularity());
+ if (startRange == null) {
+ return handleGestureFailure(gesture);
+ }
+ int[] endRange = getRangeForRect(
+ convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
+ gesture.getGranularity());
+ if (endRange == null) {
+ return handleGestureFailure(gesture);
+ }
+ 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) {
return handleGestureFailure(gesture);
}
- int start = range.getLower();
- int end = range.getUpper();
- // For word granularity, adjust the start and end offsets to remove extra whitespace around
- // the deleted text.
if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
- // 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.
- int codePointBeforeStart = start > 0
- ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
- // If the deleted text is at the end of the text, the behavior is the same as the case
- // where the deleted text precedes a new line character.
- int codePointAtEnd = end < mText.length()
- ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
- if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
- && (TextUtils.isWhitespace(codePointAtEnd)
- || TextUtils.isPunctuation(codePointAtEnd))) {
- // Remove whitespace (except new lines) before the deleted text, in these cases:
- // - There is whitespace following the deleted text
- // e.g. "one [deleted] three" -> "one | three" -> "one| three"
- // - There is punctuation following the deleted text
- // e.g. "one [deleted]!" -> "one |!" -> "one|!"
- // - There is a new line following the deleted text
- // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
- // - The deleted text is at the end of the text
- // e.g. "one [deleted]" -> "one |" -> "one|"
- // (The pipe | indicates the cursor position.)
- do {
- start -= Character.charCount(codePointBeforeStart);
- if (start == 0) break;
- codePointBeforeStart = Character.codePointBefore(mText, start);
- } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
- } else if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
- && (TextUtils.isWhitespace(codePointBeforeStart)
- || TextUtils.isPunctuation(codePointBeforeStart))) {
- // Remove whitespace (except new lines) after the deleted text, in these cases:
- // - There is punctuation preceding the deleted text
- // e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
- // - There is a new line preceding the deleted text
- // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
- // - The deleted text is at the start of the text
- // e.g. "[deleted] two" -> "| two" -> "|two"
- // (The pipe | indicates the cursor position.)
- do {
- end += Character.charCount(codePointAtEnd);
- if (end == mText.length()) break;
- codePointAtEnd = Character.codePointAt(mText, end);
- } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
- }
- }
-
- getEditableText().delete(start, end);
- Selection.setSelection(getEditableText(), start);
+ range = adjustHandwritingDeleteGestureRange(range);
+ }
+
+ getEditableText().delete(range[0], range[1]);
+ Selection.setSelection(getEditableText(), range[0]);
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
+ public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
+ int[] startRange = getRangeForRect(
+ convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
+ gesture.getGranularity());
+ if (startRange == null) {
+ return handleGestureFailure(gesture);
+ }
+ int[] endRange = getRangeForRect(
+ convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
+ gesture.getGranularity());
+ if (endRange == null) {
+ return handleGestureFailure(gesture);
+ }
+ 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[0], range[1]);
+ Selection.setSelection(getEditableText(), range[0]);
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ }
+
+ 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[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.
+ int codePointBeforeStart = start > 0
+ ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT;
+ // If the deleted text is at the end of the text, the behavior is the same as the case where
+ // the deleted text precedes a new line character.
+ int codePointAtEnd = end < mText.length()
+ ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT;
+
+ if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)
+ && (TextUtils.isWhitespace(codePointAtEnd)
+ || TextUtils.isPunctuation(codePointAtEnd))) {
+ // Remove whitespace (except new lines) before the deleted text, in these cases:
+ // - There is whitespace following the deleted text
+ // e.g. "one [deleted] three" -> "one | three" -> "one| three"
+ // - There is punctuation following the deleted text
+ // e.g. "one [deleted]!" -> "one |!" -> "one|!"
+ // - There is a new line following the deleted text
+ // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n"
+ // - The deleted text is at the end of the text
+ // e.g. "one [deleted]" -> "one |" -> "one|"
+ // (The pipe | indicates the cursor position.)
+ do {
+ start -= Character.charCount(codePointBeforeStart);
+ if (start == 0) break;
+ codePointBeforeStart = Character.codePointBefore(mText, start);
+ } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart));
+ return new int[] {start, end};
+ }
+
+ if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)
+ && (TextUtils.isWhitespace(codePointBeforeStart)
+ || TextUtils.isPunctuation(codePointBeforeStart))) {
+ // Remove whitespace (except new lines) after the deleted text, in these cases:
+ // - There is punctuation preceding the deleted text
+ // e.g. "([deleted] two)" -> "(| two)" -> "(|two)"
+ // - There is a new line preceding the deleted text
+ // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two"
+ // - The deleted text is at the start of the text
+ // e.g. "[deleted] two" -> "| two" -> "|two"
+ // (The pipe | indicates the cursor position.)
+ do {
+ end += Character.charCount(codePointAtEnd);
+ if (end == mText.length()) break;
+ codePointAtEnd = Character.codePointAt(mText, end);
+ } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd));
+ return new int[] {start, end};
+ }
+
+ // Return the original range.
+ return range;
+ }
+
+ /** @hide */
public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
int line = getLineForHandwritingGesture(point);
@@ -9431,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();
@@ -9543,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/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index f09e176beea0..21c7baab4e83 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,6 +16,7 @@
package com.android.internal.backup;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
@@ -400,4 +401,13 @@ oneway interface IBackupTransport {
* <p>For supported flags see {@link android.app.backup.BackupAgent}.
*/
void getTransportFlags(in AndroidFuture<int> resultFuture);
+
+ /**
+ * Ask the transport for a {@link IBackupManagerMonitor} instance which will be used by the
+ * framework to report logging events back to the transport.
+ *
+ * Backups requested from outside the framework may pass in a monitor with the request,
+ * however backups initiated by the framework will call this method to retrieve one.
+ */
+ void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture);
}
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index f260d7dfc6a6..f600c36cd8c9 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -35,6 +35,7 @@ import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.DeleteGesture;
+import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.DumpableInputConnection;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -44,6 +45,7 @@ import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
+import android.view.inputmethod.SelectRangeGesture;
import android.widget.TextView;
import java.util.concurrent.Executor;
@@ -275,8 +277,12 @@ public final class EditableInputConnection extends BaseInputConnection
int result;
if (gesture instanceof SelectGesture) {
result = mTextView.performHandwritingSelectGesture((SelectGesture) gesture);
+ } else if (gesture instanceof SelectRangeGesture) {
+ result = mTextView.performHandwritingSelectRangeGesture((SelectRangeGesture) gesture);
} else if (gesture instanceof DeleteGesture) {
result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture);
+ } else if (gesture instanceof DeleteRangeGesture) {
+ result = mTextView.performHandwritingDeleteRangeGesture((DeleteRangeGesture) gesture);
} else if (gesture instanceof InsertGesture) {
result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture);
} else if (gesture instanceof RemoveSpaceGesture) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 09c97b39e260..1b4afd6dd39f 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -49,6 +49,8 @@ public final class InputMethodDebug {
return "WINDOW_FOCUS_GAIN";
case StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY:
return "WINDOW_FOCUS_GAIN_REPORT_ONLY";
+ case StartInputReason.SCHEDULED_CHECK_FOCUS:
+ return "SCHEDULED_CHECK_FOCUS";
case StartInputReason.APP_CALLED_RESTART_INPUT_API:
return "APP_CALLED_RESTART_INPUT_API";
case StartInputReason.CHECK_FOCUS:
diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java
index 51ed841410d7..733d9751c810 100644
--- a/core/java/com/android/internal/inputmethod/StartInputReason.java
+++ b/core/java/com/android/internal/inputmethod/StartInputReason.java
@@ -31,6 +31,7 @@ import java.lang.annotation.Retention;
StartInputReason.UNSPECIFIED,
StartInputReason.WINDOW_FOCUS_GAIN,
StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY,
+ StartInputReason.SCHEDULED_CHECK_FOCUS,
StartInputReason.APP_CALLED_RESTART_INPUT_API,
StartInputReason.CHECK_FOCUS,
StartInputReason.BOUND_TO_IMMS,
@@ -58,6 +59,11 @@ public @interface StartInputReason {
*/
int WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;
/**
+ * Similar to {@link #CHECK_FOCUS}, but the one scheduled with
+ * {@link android.view.ViewRootImpl#dispatchCheckFocus()}.
+ */
+ int SCHEDULED_CHECK_FOCUS = 3;
+ /**
* {@link android.view.inputmethod.InputMethodManager#restartInput(android.view.View)} is
* either explicitly called by the application or indirectly called by some Framework class
* (e.g. {@link android.widget.EditText}).
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/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 681b46a01c8d..0489dc812ab6 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -35,7 +35,10 @@ import java.util.List;
// Manages the NotificationChannels used by the frameworks itself.
public class SystemNotificationChannels {
- public static String VIRTUAL_KEYBOARD = "VIRTUAL_KEYBOARD";
+ /**
+ * @deprecated Legacy system channel, which is no longer used,
+ */
+ @Deprecated public static String VIRTUAL_KEYBOARD = "VIRTUAL_KEYBOARD";
public static String PHYSICAL_KEYBOARD = "PHYSICAL_KEYBOARD";
public static String SECURITY = "SECURITY";
public static String CAR_MODE = "CAR_MODE";
@@ -72,13 +75,6 @@ public class SystemNotificationChannels {
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
List<NotificationChannel> channelsList = new ArrayList<NotificationChannel>();
- final NotificationChannel keyboard = new NotificationChannel(
- VIRTUAL_KEYBOARD,
- context.getString(R.string.notification_channel_virtual_keyboard),
- NotificationManager.IMPORTANCE_LOW);
- keyboard.setBlockable(true);
- channelsList.add(keyboard);
-
final NotificationChannel physicalKeyboardChannel = new NotificationChannel(
PHYSICAL_KEYBOARD,
context.getString(R.string.notification_channel_physical_keyboard),
@@ -237,6 +233,7 @@ public class SystemNotificationChannels {
/** Remove notification channels which are no longer used */
public static void removeDeprecated(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.deleteNotificationChannel(VIRTUAL_KEYBOARD);
nm.deleteNotificationChannel(DEVICE_ADMIN_DEPRECATED);
nm.deleteNotificationChannel(SYSTEM_CHANGES_DEPRECATED);
}
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 1f23eb6a86d6..eb54abfbbcbe 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -668,7 +668,6 @@
<protected-broadcast android:name="android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED" />
<protected-broadcast android:name="android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
<protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
- <protected-broadcast android:name="com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
<!-- Time zone rules update intents fired by the system server -->
<protected-broadcast android:name="com.android.intent.action.timezone.RULES_UPDATE_OPERATION" />
@@ -4261,6 +4260,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.
@@ -6571,6 +6577,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/drawable-hdpi/ic_notification_ime_default.png b/core/res/res/drawable-hdpi/ic_notification_ime_default.png
deleted file mode 100644
index 369c88d31707..000000000000
--- a/core/res/res/drawable-hdpi/ic_notification_ime_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_ime_default.png b/core/res/res/drawable-mdpi/ic_notification_ime_default.png
deleted file mode 100644
index 7d97eb575f2a..000000000000
--- a/core/res/res/drawable-mdpi/ic_notification_ime_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_ime_default.png b/core/res/res/drawable-xhdpi/ic_notification_ime_default.png
deleted file mode 100644
index 900801a4d3d7..000000000000
--- a/core/res/res/drawable-xhdpi/ic_notification_ime_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_ime_default.png b/core/res/res/drawable-xxhdpi/ic_notification_ime_default.png
deleted file mode 100644
index 6c8222ec50b7..000000000000
--- a/core/res/res/drawable-xxhdpi/ic_notification_ime_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a7f2aa7cba69..be1c939f0ff8 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -24,6 +24,7 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no"
>
<ImageView
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b5cdcff76ed1..caa67de1fe61 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3559,9 +3559,9 @@
config_sidefpsSkipWaitForPowerVendorAcquireMessage -->
<integer name="config_sidefpsSkipWaitForPowerAcquireMessage">6</integer>
- <!-- This vendor acquired message that will cause the sidefpsKgPowerPress window to be skipped.
- config_sidefpsSkipWaitForPowerOnFingerUp must be true and
- config_sidefpsSkipWaitForPowerAcquireMessage must be BIOMETRIC_ACQUIRED_VENDOR == 6. -->
+ <!-- This vendor acquired message will cause the sidefpsKgPowerPress window to be skipped
+ when config_sidefpsSkipWaitForPowerAcquireMessage == 6 (VENDOR) and the vendor acquire
+ message equals this constant -->
<integer name="config_sidefpsSkipWaitForPowerVendorAcquireMessage">2</integer>
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index a1d73ff25cb8..71b2f00ff2b1 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -117,4 +117,17 @@
<!-- Whether using the new SubscriptionManagerService or the old SubscriptionController -->
<bool name="config_using_subscription_manager_service">false</bool>
<java-symbol type="bool" name="config_using_subscription_manager_service" />
+
+ <!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config,
+ should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in
+ Android Emergency DB. If this value is true, emergency numbers for a country, sourced from
+ modem/config, will be ignored if that country is 'locked' in Android Emergency DB. -->
+ <bool name="ignore_modem_config_emergency_numbers">false</bool>
+ <java-symbol type="bool" name="ignore_modem_config_emergency_numbers" />
+
+ <!-- Boolean indicating whether emergency numbers routing from the android emergency number
+ database should be ignored (i.e. routing will always be set to UNKNOWN). If this value is
+ true, routing from the android emergency number database will be ignored. -->
+ <bool name="ignore_emergency_number_routing_from_db">false</bool>
+ <java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5f9911358921..d0fca8b0a7bb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -741,9 +741,6 @@
<!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
<string name="notification_hidden_text">New notification</string>
- <!-- Text shown when viewing channel settings for notifications related to the virtual keyboard -->
- <string name="notification_channel_virtual_keyboard">Virtual keyboard</string>
-
<!-- Text shown when viewing channel settings for notifications related to the hardware keyboard -->
<string name="notification_channel_physical_keyboard">Physical keyboard</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b94d7990b394..fc55ed2fe443 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1992,7 +1992,6 @@
<java-symbol type="color" name="config_defaultNotificationColor" />
<java-symbol type="color" name="decor_view_status_guard" />
<java-symbol type="color" name="decor_view_status_guard_light" />
- <java-symbol type="drawable" name="ic_notification_ime_default" />
<java-symbol type="drawable" name="ic_menu_refresh" />
<java-symbol type="drawable" name="ic_settings" />
<java-symbol type="drawable" name="ic_voice_search" />
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 113f45dfef55..7cb64c8b4f0c 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -23,23 +23,32 @@ package {
android_test {
name: "BroadcastRadioTests",
+ srcs: ["src/**/*.java"],
privileged: true,
certificate: "platform",
// TODO(b/13282254): uncomment when b/13282254 is fixed
// sdk_version: "current"
platform_apis: true,
- static_libs: [
- "compatibility-device-util-axt",
- "androidx.test.rules",
- "testng",
- "services.core",
- ],
- libs: ["android.test.base"],
- srcs: ["src/**/*.java"],
dex_preopt: {
enabled: false,
},
optimize: {
enabled: false,
},
+ static_libs: [
+ "services.core",
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "testng",
+ "mockito-target-extended",
+ ],
+ libs: ["android.test.base"],
+ test_suites: [
+ "general-tests",
+ ],
+ // mockito-target-inline dependency
+ jni_libs: [
+ "libcarservicejni",
+ "libdexmakerjvmtiagent",
+ ],
}
diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml
index ce12cc99946f..869b4844e529 100644
--- a/core/tests/BroadcastRadioTests/AndroidManifest.xml
+++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml
@@ -19,7 +19,7 @@
<uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index 11eb158317e9..3f35e998e025 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -33,11 +33,9 @@ import android.content.pm.PackageManager;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
-import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -47,6 +45,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.HashMap;
@@ -56,8 +55,7 @@ import java.util.Map;
/**
* A test for broadcast radio API.
*/
-@RunWith(AndroidJUnit4.class)
-@MediumTest
+@RunWith(MockitoJUnitRunner.class)
public class RadioTunerTest {
private static final String TAG = "BroadcastRadioTests.RadioTuner";
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
new file mode 100644
index 000000000000..9bfa2fba6948
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -0,0 +1,648 @@
+/*
+ * 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 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;
+ private static final int FM_LOWER_LIMIT = 87500;
+ private static final int FM_UPPER_LIMIT = 108000;
+ private static final int FM_SPACING = 200;
+ private static final int AM_LOWER_LIMIT = 540;
+ private static final int AM_UPPER_LIMIT = 1700;
+ private static final int AM_SPACING = 10;
+ private static final boolean STEREO_SUPPORTED = true;
+ private static final boolean RDS_SUPPORTED = true;
+ private static final boolean TA_SUPPORTED = false;
+ private static final boolean AF_SUPPORTED = false;
+ private static final boolean EA_SUPPORTED = false;
+
+ private static final int PROPERTIES_ID = 10;
+ private static final String SERVICE_NAME = "ServiceNameMock";
+ private static final int CLASS_ID = RadioManager.CLASS_AM_FM;
+ private static final String IMPLEMENTOR = "ImplementorMock";
+ private static final String PRODUCT = "ProductMock";
+ private static final String VERSION = "VersionMock";
+ private static final String SERIAL = "SerialMock";
+ private static final int NUM_TUNERS = 1;
+ private static final int NUM_AUDIO_SOURCES = 1;
+ private static final boolean IS_INITIALIZATION_REQUIRED = false;
+ private static final boolean IS_CAPTURE_SUPPORTED = false;
+ private static final boolean IS_BG_SCAN_SUPPORTED = true;
+ private static final int[] SUPPORTED_PROGRAM_TYPES = new int[]{
+ ProgramSelector.PROGRAM_TYPE_AM, ProgramSelector.PROGRAM_TYPE_FM};
+ private static final int[] SUPPORTED_IDENTIFIERS_TYPES = new int[]{
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI};
+
+ private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
+ createFmBandDescriptor();
+ private static final RadioManager.AmBandDescriptor AM_BAND_DESCRIPTOR =
+ createAmBandDescriptor();
+ private static final RadioManager.FmBandConfig FM_BAND_CONFIG = createFmBandConfig();
+ 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();
+
+ assertWithMessage("AM Band Descriptor type")
+ .that(bandDescriptor.getType()).isEqualTo(RadioManager.BAND_AM);
+ }
+
+ @Test
+ public void getRegion_forBandDescriptor() {
+ RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+ assertWithMessage("FM Band Descriptor region")
+ .that(bandDescriptor.getRegion()).isEqualTo(REGION);
+ }
+
+ @Test
+ public void getLowerLimit_forBandDescriptor() {
+ RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+ assertWithMessage("FM Band Descriptor lower limit")
+ .that(bandDescriptor.getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
+ }
+
+ @Test
+ public void getUpperLimit_forBandDescriptor() {
+ RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
+
+ assertWithMessage("AM Band Descriptor upper limit")
+ .that(bandDescriptor.getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
+ }
+
+ @Test
+ public void getSpacing_forBandDescriptor() {
+ RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
+
+ assertWithMessage("AM Band Descriptor spacing")
+ .that(bandDescriptor.getSpacing()).isEqualTo(AM_SPACING);
+ }
+
+ @Test
+ public void isAmBand_forAmBandDescriptor_returnsTrue() {
+ RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
+
+ assertWithMessage("Is AM Band Descriptor an AM band")
+ .that(bandDescriptor.isAmBand()).isTrue();
+ }
+
+ @Test
+ public void isFmBand_forAmBandDescriptor_returnsFalse() {
+ RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
+
+ assertWithMessage("Is AM Band Descriptor an FM band")
+ .that(bandDescriptor.isFmBand()).isFalse();
+ }
+
+ @Test
+ public void isStereoSupported_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor stereo")
+ .that(FM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED);
+ }
+
+ @Test
+ public void isRdsSupported_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor RDS or RBDS")
+ .that(FM_BAND_DESCRIPTOR.isRdsSupported()).isEqualTo(RDS_SUPPORTED);
+ }
+
+ @Test
+ public void isTaSupported_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor traffic announcement")
+ .that(FM_BAND_DESCRIPTOR.isTaSupported()).isEqualTo(TA_SUPPORTED);
+ }
+
+ @Test
+ public void isAfSupported_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor alternate frequency")
+ .that(FM_BAND_DESCRIPTOR.isAfSupported()).isEqualTo(AF_SUPPORTED);
+ }
+
+ @Test
+ public void isEaSupported_forFmBandDescriptor() {
+ assertWithMessage("FM Band Descriptor emergency announcement")
+ .that(FM_BAND_DESCRIPTOR.isEaSupported()).isEqualTo(EA_SUPPORTED);
+ }
+
+ @Test
+ public void isStereoSupported_forAmBandDescriptor() {
+ assertWithMessage("AM Band Descriptor stereo")
+ .that(AM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED);
+ }
+
+ @Test
+ public void equals_withSameFmBandDescriptors_returnsTrue() {
+ RadioManager.FmBandDescriptor fmBandDescriptor1 = createFmBandDescriptor();
+ RadioManager.FmBandDescriptor fmBandDescriptor2 = createFmBandDescriptor();
+
+ assertWithMessage("The same FM Band Descriptor")
+ .that(fmBandDescriptor1).isEqualTo(fmBandDescriptor2);
+ }
+
+ @Test
+ public void equals_withSameAmBandDescriptors_returnsTrue() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared = createAmBandDescriptor();
+
+ assertWithMessage("The same AM Band Descriptor")
+ .that(AM_BAND_DESCRIPTOR).isEqualTo(amBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared =
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT + AM_SPACING, AM_SPACING, STEREO_SUPPORTED);
+
+ assertWithMessage("AM Band Descriptor of different upper limit")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withAmAndFmBandDescriptors_returnsFalse() {
+ assertWithMessage("AM Band Descriptor")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void getType_forBandConfig() {
+ RadioManager.BandConfig fmBandConfig = createFmBandConfig();
+
+ assertWithMessage("FM Band Config type")
+ .that(fmBandConfig.getType()).isEqualTo(RadioManager.BAND_FM);
+ }
+
+ @Test
+ public void getRegion_forBandConfig() {
+ RadioManager.BandConfig amBandConfig = createAmBandConfig();
+
+ assertWithMessage("AM Band Config region")
+ .that(amBandConfig.getRegion()).isEqualTo(REGION);
+ }
+
+ @Test
+ public void getLowerLimit_forBandConfig() {
+ RadioManager.BandConfig amBandConfig = createAmBandConfig();
+
+ assertWithMessage("AM Band Config lower limit")
+ .that(amBandConfig.getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
+ }
+
+ @Test
+ public void getUpperLimit_forBandConfig() {
+ RadioManager.BandConfig fmBandConfig = createFmBandConfig();
+
+ assertWithMessage("FM Band Config upper limit")
+ .that(fmBandConfig.getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
+ }
+
+ @Test
+ public void getSpacing_forBandConfig() {
+ RadioManager.BandConfig fmBandConfig = createFmBandConfig();
+
+ assertWithMessage("FM Band Config spacing")
+ .that(fmBandConfig.getSpacing()).isEqualTo(FM_SPACING);
+ }
+
+ @Test
+ public void getStereo_forFmBandConfig() {
+ assertWithMessage("FM Band Config stereo ")
+ .that(FM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
+ }
+
+ @Test
+ public void getRds_forFmBandConfig() {
+ assertWithMessage("FM Band Config RDS or RBDS")
+ .that(FM_BAND_CONFIG.getRds()).isEqualTo(RDS_SUPPORTED);
+ }
+
+ @Test
+ public void getTa_forFmBandConfig() {
+ assertWithMessage("FM Band Config traffic announcement")
+ .that(FM_BAND_CONFIG.getTa()).isEqualTo(TA_SUPPORTED);
+ }
+
+ @Test
+ public void getAf_forFmBandConfig() {
+ assertWithMessage("FM Band Config alternate frequency")
+ .that(FM_BAND_CONFIG.getAf()).isEqualTo(AF_SUPPORTED);
+ }
+
+ @Test
+ public void getEa_forFmBandConfig() {
+ assertWithMessage("FM Band Config emergency Announcement")
+ .that(FM_BAND_CONFIG.getEa()).isEqualTo(EA_SUPPORTED);
+ }
+
+ @Test
+ public void getStereo_forAmBandConfig() {
+ assertWithMessage("AM Band Config stereo")
+ .that(AM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
+ }
+
+ @Test
+ public void equals_withSameFmBandConfigs_returnsTrue() {
+ RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig();
+
+ assertWithMessage("The same FM Band Config")
+ .that(FM_BAND_CONFIG).isEqualTo(fmBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() {
+ RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
+ createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
+ .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
+ RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build();
+
+ assertWithMessage("FM Band Config of different af value")
+ .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder);
+ }
+
+ @Test
+ public void equals_withFmAndAmBandConfigs_returnsFalse() {
+ assertWithMessage("FM Band Config")
+ .that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withSameAmBandConfigs_returnsTrue() {
+ RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig();
+
+ assertWithMessage("The same AM Band Config")
+ .that(AM_BAND_CONFIG).isEqualTo(amBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withAmBandConfigsOfDifferentTypes_returnsFalse() {
+ RadioManager.AmBandConfig amBandConfigCompared = new RadioManager.AmBandConfig(
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED));
+
+ assertWithMessage("AM Band Config of different type")
+ .that(AM_BAND_CONFIG).isNotEqualTo(amBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withAmBandConfigsOfDifferentStereoValues_returnsFalse() {
+ RadioManager.AmBandConfig.Builder builder = new RadioManager.AmBandConfig.Builder(
+ createAmBandDescriptor()).setStereo(!STEREO_SUPPORTED);
+ RadioManager.AmBandConfig amBandConfigFromBuilder = builder.build();
+
+ assertWithMessage("AM Band Config of different stereo value")
+ .that(AM_BAND_CONFIG).isNotEqualTo(amBandConfigFromBuilder);
+ }
+
+ @Test
+ public void getId_forModuleProperties() {
+ assertWithMessage("Properties id")
+ .that(AMFM_PROPERTIES.getId()).isEqualTo(PROPERTIES_ID);
+ }
+
+ @Test
+ public void getServiceName_forModuleProperties() {
+ assertWithMessage("Properties service name")
+ .that(AMFM_PROPERTIES.getServiceName()).isEqualTo(SERVICE_NAME);
+ }
+
+ @Test
+ public void getClassId_forModuleProperties() {
+ assertWithMessage("Properties class ID")
+ .that(AMFM_PROPERTIES.getClassId()).isEqualTo(CLASS_ID);
+ }
+
+ @Test
+ public void getImplementor_forModuleProperties() {
+ assertWithMessage("Properties implementor")
+ .that(AMFM_PROPERTIES.getImplementor()).isEqualTo(IMPLEMENTOR);
+ }
+
+ @Test
+ public void getProduct_forModuleProperties() {
+ assertWithMessage("Properties product")
+ .that(AMFM_PROPERTIES.getProduct()).isEqualTo(PRODUCT);
+ }
+
+ @Test
+ public void getVersion_forModuleProperties() {
+ assertWithMessage("Properties version")
+ .that(AMFM_PROPERTIES.getVersion()).isEqualTo(VERSION);
+ }
+
+ @Test
+ public void getSerial_forModuleProperties() {
+ assertWithMessage("Serial properties")
+ .that(AMFM_PROPERTIES.getSerial()).isEqualTo(SERIAL);
+ }
+
+ @Test
+ public void getNumTuners_forModuleProperties() {
+ assertWithMessage("Number of tuners in properties")
+ .that(AMFM_PROPERTIES.getNumTuners()).isEqualTo(NUM_TUNERS);
+ }
+
+ @Test
+ public void getNumAudioSources_forModuleProperties() {
+ assertWithMessage("Number of audio sources in properties")
+ .that(AMFM_PROPERTIES.getNumAudioSources()).isEqualTo(NUM_AUDIO_SOURCES);
+ }
+
+ @Test
+ public void isInitializationRequired_forModuleProperties() {
+ assertWithMessage("Initialization required in properties")
+ .that(AMFM_PROPERTIES.isInitializationRequired())
+ .isEqualTo(IS_INITIALIZATION_REQUIRED);
+ }
+
+ @Test
+ public void isCaptureSupported_forModuleProperties() {
+ assertWithMessage("Capture support in properties")
+ .that(AMFM_PROPERTIES.isCaptureSupported()).isEqualTo(IS_CAPTURE_SUPPORTED);
+ }
+
+ @Test
+ public void isBackgroundScanningSupported_forModuleProperties() {
+ assertWithMessage("Background scan support in properties")
+ .that(AMFM_PROPERTIES.isBackgroundScanningSupported())
+ .isEqualTo(IS_BG_SCAN_SUPPORTED);
+ }
+
+ @Test
+ public void isProgramTypeSupported_withSupportedType_forModuleProperties() {
+ assertWithMessage("AM/FM frequency type radio support in properties")
+ .that(AMFM_PROPERTIES.isProgramTypeSupported(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY))
+ .isTrue();
+ }
+
+ @Test
+ public void isProgramTypeSupported_withNonSupportedType_forModuleProperties() {
+ assertWithMessage("DAB frequency type radio support in properties")
+ .that(AMFM_PROPERTIES.isProgramTypeSupported(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY)).isFalse();
+ }
+
+ @Test
+ public void isProgramIdentifierSupported_withSupportedIdentifier_forModuleProperties() {
+ assertWithMessage("AM/FM frequency identifier radio support in properties")
+ .that(AMFM_PROPERTIES.isProgramIdentifierSupported(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)).isTrue();
+ }
+
+ @Test
+ public void isProgramIdentifierSupported_withNonSupportedIdentifier_forModuleProperties() {
+ assertWithMessage("DAB frequency identifier radio support in properties")
+ .that(AMFM_PROPERTIES.isProgramIdentifierSupported(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY)).isFalse();
+ }
+
+ @Test
+ public void getDabFrequencyTable_forModuleProperties() {
+ assertWithMessage("Properties DAB frequency table")
+ .that(AMFM_PROPERTIES.getDabFrequencyTable()).isNull();
+ }
+
+ @Test
+ public void getVendorInfo_forModuleProperties() {
+ assertWithMessage("Properties vendor info")
+ .that(AMFM_PROPERTIES.getVendorInfo()).isEmpty();
+ }
+
+ @Test
+ public void getBands_forModuleProperties() {
+ assertWithMessage("Properties bands")
+ .that(AMFM_PROPERTIES.getBands()).asList()
+ .containsExactly(AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withSameProperties_returnsTrue() {
+ RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+
+ assertWithMessage("The same module properties")
+ .that(AMFM_PROPERTIES).isEqualTo(propertiesCompared);
+ }
+
+ @Test
+ public void equals_withModulePropertiesOfDifferentIds_returnsFalse() {
+ RadioManager.ModuleProperties propertiesDab = new RadioManager.ModuleProperties(
+ PROPERTIES_ID + 1, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION,
+ SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, IS_INITIALIZATION_REQUIRED,
+ IS_CAPTURE_SUPPORTED, /* bands= */ null, IS_BG_SCAN_SUPPORTED,
+ SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, /* dabFrequencyTable= */ null,
+ /* vendorInfo= */ null);
+
+ assertWithMessage("Module properties of different id")
+ .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,
+ IS_INITIALIZATION_REQUIRED, IS_CAPTURE_SUPPORTED,
+ new RadioManager.BandDescriptor[]{AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR},
+ IS_BG_SCAN_SUPPORTED, SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES,
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+ }
+
+ private static RadioManager.FmBandDescriptor createFmBandDescriptor() {
+ return new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED);
+ }
+
+ private static RadioManager.AmBandDescriptor createAmBandDescriptor() {
+ return new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED);
+ }
+
+ private static RadioManager.FmBandConfig createFmBandConfig() {
+ return new RadioManager.FmBandConfig(createFmBandDescriptor());
+ }
+
+ 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/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
new file mode 100644
index 000000000000..7f4ea1170084
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.broadcastradio;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+
+import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class IRadioServiceAidlImplTest {
+
+ private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private IRadioServiceAidlImpl mAidlImpl;
+
+ @Mock
+ private BroadcastRadioService mServiceMock;
+ @Mock
+ private BroadcastRadioServiceImpl mHalMock;
+ @Mock
+ private RadioManager.ModuleProperties mModuleMock;
+ @Mock
+ private RadioManager.BandConfig mBandConfigMock;
+ @Mock
+ private ITunerCallback mTunerCallbackMock;
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private ICloseHandle mICloseHandle;
+ @Mock
+ private ITuner mTunerMock;
+
+ @Before
+ public void setUp() throws Exception {
+ doNothing().when(mServiceMock).enforcePolicyAccess();
+
+ when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock));
+ when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
+ .thenReturn(mTunerMock);
+ when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
+
+ mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock);
+ }
+
+ @Test
+ public void loadModules_forAidlImpl() {
+ assertWithMessage("Modules loaded in AIDL HAL")
+ .that(mAidlImpl.listModules())
+ .containsExactly(mModuleMock);
+ }
+
+ @Test
+ public void openTuner_forAidlImpl() throws Exception {
+ ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
+ /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Tuner opened in AIDL HAL")
+ .that(tuner).isEqualTo(mTunerMock);
+ }
+
+ @Test
+ public void addAnnouncementListener_forAidlImpl() {
+ ICloseHandle closeHandle = mAidlImpl.addAnnouncementListener(ENABLE_TYPES, mListenerMock);
+
+ verify(mHalMock).addAnnouncementListener(ENABLE_TYPES, mListenerMock);
+ assertWithMessage("Close handle of announcement listener for HAL 2")
+ .that(closeHandle).isEqualTo(mICloseHandle);
+ }
+
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
new file mode 100644
index 000000000000..f28e27d7f896
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.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.server.broadcastradio;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link android.hardware.radio.IRadioService} with HIDL HAL implementation
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class IRadioServiceHidlImplTest {
+
+ private static final int HAL1_MODULE_ID = 0;
+ private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private IRadioServiceHidlImpl mHidlImpl;
+
+ @Mock
+ private BroadcastRadioService mServiceMock;
+ @Mock
+ private com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1Mock;
+ @Mock
+ private com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2Mock;
+ @Mock
+ private RadioManager.ModuleProperties mHal1ModuleMock;
+ @Mock
+ private RadioManager.ModuleProperties mHal2ModuleMock;
+ @Mock
+ private RadioManager.BandConfig mBandConfigMock;
+ @Mock
+ private ITunerCallback mTunerCallbackMock;
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private ICloseHandle mICloseHandle;
+ @Mock
+ private ITuner mHal1TunerMock;
+ @Mock
+ private ITuner mHal2TunerMock;
+
+ @Before
+ public void setup() throws Exception {
+ doNothing().when(mServiceMock).enforcePolicyAccess();
+ when(mHal1Mock.loadModules()).thenReturn(Arrays.asList(mHal1ModuleMock));
+ when(mHal1Mock.openTuner(anyInt(), any(), anyBoolean(), any())).thenReturn(mHal1TunerMock);
+
+ when(mHal2Mock.listModules()).thenReturn(Arrays.asList(mHal2ModuleMock));
+ doAnswer(invocation -> {
+ int moduleId = (int) invocation.getArguments()[0];
+ return moduleId != HAL1_MODULE_ID;
+ }).when(mHal2Mock).hasModule(anyInt());
+ when(mHal2Mock.openSession(anyInt(), any(), anyBoolean(), any()))
+ .thenReturn(mHal2TunerMock);
+ when(mHal2Mock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
+
+ mHidlImpl = new IRadioServiceHidlImpl(mServiceMock, mHal1Mock, mHal2Mock);
+ }
+
+ @Test
+ public void loadModules_forHidlImpl() {
+ assertWithMessage("Modules loaded in HIDL HAL")
+ .that(mHidlImpl.listModules())
+ .containsExactly(mHal1ModuleMock, mHal2ModuleMock);
+ }
+
+ @Test
+ public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
+ ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
+ /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Tuner opened in HAL 1")
+ .that(tuner).isEqualTo(mHal1TunerMock);
+ }
+
+ @Test
+ public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
+ ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
+ /* withAudio= */ true, mTunerCallbackMock);
+
+ assertWithMessage("Tuner opened in HAL 2")
+ .that(tuner).isEqualTo(mHal2TunerMock);
+ }
+
+ @Test
+ public void addAnnouncementListener_forHidlImpl() {
+ when(mHal2Mock.hasAnyModules()).thenReturn(true);
+ ICloseHandle closeHandle = mHidlImpl.addAnnouncementListener(ENABLE_TYPES, mListenerMock);
+
+ verify(mHal2Mock).addAnnouncementListener(ENABLE_TYPES, mListenerMock);
+ assertWithMessage("Close handle of announcement listener for HAL 2")
+ .that(closeHandle).isEqualTo(mICloseHandle);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0b8b29b9dda9..bcb13d2108b8 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -48,6 +48,7 @@ import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWi
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
@@ -56,7 +57,9 @@ import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.Notification.CallStyle;
@@ -68,6 +71,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
@@ -79,7 +83,9 @@ import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
+import android.util.Pair;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +95,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.util.ContrastColorUtil;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -218,8 +226,10 @@ public class NotificationTest {
@Test
public void allPendingIntents_recollectedAfterReusingBuilder() {
- PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
- PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent intent2 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);
Notification.Builder builder = new Notification.Builder(mContext, "channel");
builder.setContentIntent(intent1);
@@ -669,30 +679,23 @@ public class NotificationTest {
Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
style).build();
+ int targetSize = mContext.getResources().getDimensionPixelSize(
+ ActivityManager.isLowRamDeviceStatic()
+ ? R.dimen.notification_person_icon_max_size_low_ram
+ : R.dimen.notification_person_icon_max_size);
+
Bitmap personIcon = style.getUser().getIcon().getBitmap();
- assertThat(personIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(personIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(personIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(personIcon.getHeight()).isEqualTo(targetSize);
Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
- assertThat(avatarIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(avatarIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(avatarIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);
Bitmap historicAvatarIcon = style.getHistoricMessages().get(
0).getSenderPerson().getIcon().getBitmap();
- assertThat(historicAvatarIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(historicAvatarIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);
}
@Test
@@ -780,7 +783,6 @@ public class NotificationTest {
assertFalse(notification.isMediaNotification());
}
- @Test
public void validateColorizedPaletteForColor(int rawColor) {
Notification.Colors cDay = new Notification.Colors();
Notification.Colors cNight = new Notification.Colors();
@@ -861,19 +863,22 @@ public class NotificationTest {
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
- style.restoreFromExtras(fakeTypes);
// no crash, good
}
@Test
public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() {
- Notification.Style style = new Notification.MessagingStyle();
+ Notification.Style style = new Notification.MessagingStyle("test");
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle());
- style.restoreFromExtras(fakeTypes);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@@ -885,22 +890,33 @@ public class NotificationTest {
fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle());
- style.restoreFromExtras(fakeTypes);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@Test
public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
- Notification.Style style = new CallStyle();
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Notification.Style style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("hi").build(), intent1, intent1);
+
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());
fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle());
- style.restoreFromExtras(fakeTypes);
-
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@@ -962,7 +978,11 @@ public class NotificationTest {
fakeTypes.putParcelable(KEY_ON_READ, new Bundle());
fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());
fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
- Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes);
+
+ Notification n = new Notification.Builder(mContext, "test")
+ .setExtras(fakeTypes)
+ .build();
+ Notification.CarExtender extender = new Notification.CarExtender(n);
// no crash, good
}
@@ -980,6 +1000,493 @@ public class NotificationTest {
// no crash, good
}
+
+ @Test
+ public void testDoesNotStripsExtenders() {
+ Notification.Builder nb = new Notification.Builder(mContext, "channel");
+ nb.extend(new Notification.CarExtender().setColor(Color.RED));
+ nb.extend(new Notification.TvExtender().setChannelId("different channel"));
+ nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
+ Notification before = nb.build();
+ Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
+
+ assertTrue(before == after);
+
+ Assert.assertEquals("different channel",
+ new Notification.TvExtender(before).getChannelId());
+ Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
+ Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_noStyles() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test");
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+
+ assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test");
+ Notification.Builder n2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_changeStyle() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle());
+ Notification.Builder n2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testInboxTextChange() {
+ Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
+ Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
+ }
+
+ @Test
+ public void testBigTextTextChange() {
+ Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().bigText("something"));
+ Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().bigText("else"));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
+ }
+
+ @Test
+ public void testBigPictureChange() {
+ Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+
+ Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
+ Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
+ }
+
+ @Test
+ public void testMessagingChange_text() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build()))
+ .addMessage(new Notification.MessagingStyle.Message(
+ "b", 100, new Person.Builder().setName("hi").build()))
+ );
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_data() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())
+ .setData("text", mock(Uri.class))));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_sender() {
+ Person a = new Person.Builder().setName("A").build();
+ Person b = new Person.Builder().setName("b").build();
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_key() {
+ Person a = new Person.Builder().setName("hi").setKey("A").build();
+ Person b = new Person.Builder().setName("hi").setKey("b").build();
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_ignoreTimeChange() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 1000, new Person.Builder().setName("hi").build()))
+ );
+
+ assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testRemoteViews_nullChange() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setContent(mock(RemoteViews.class));
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test")
+ .setContent(mock(RemoteViews.class));
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test")
+ .setCustomBigContentView(mock(RemoteViews.class));
+ n2 = new Notification.Builder(mContext, "test");
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test")
+ .setCustomBigContentView(mock(RemoteViews.class));
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test");
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_layoutChange() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(189);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_layoutSame() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_sequenceChange() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ when(a.getSequenceNumber()).thenReturn(1);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+ when(b.getSequenceNumber()).thenReturn(2);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_sequenceSame() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ when(a.getSequenceNumber()).thenReturn(1);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+ when(b.getSequenceNumber()).thenReturn(1);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferent_null() {
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentSame() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentText() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+ .build();
+
+ assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentSpannables() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon,
+ new SpannableStringBuilder().append("test1",
+ new StyleSpan(Typeface.BOLD),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
+ intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentNumber() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+ .build();
+
+ assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentIntent() {
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent intent2 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsIgnoresRemoteInputs() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(new RemoteInput.Builder("a")
+ .setChoices(new CharSequence[] {"i", "m"})
+ .build())
+ .build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(new RemoteInput.Builder("a")
+ .setChoices(new CharSequence[] {"t", "m"})
+ .build())
+ .build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_noRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .build())
+ .build();
+ Assert.assertNull(notification.findRemoteInputActionPair(false));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_hasRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ RemoteInput remoteInput = new RemoteInput.Builder("a").build();
+
+ Notification.Action actionWithRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(remoteInput)
+ .addRemoteInput(remoteInput)
+ .build();
+
+ Notification.Action actionWithoutRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 2", intent)
+ .build();
+
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(actionWithoutRemoteInput)
+ .addAction(actionWithRemoteInput)
+ .build();
+
+ Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+ notification.findRemoteInputActionPair(false);
+
+ assertNotNull(remoteInputActionPair);
+ Assert.assertEquals(remoteInput, remoteInputActionPair.first);
+ Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(
+ new RemoteInput.Builder("a")
+ .setAllowFreeFormInput(false).build())
+ .build())
+ .build();
+ Assert.assertNull(notification.findRemoteInputActionPair(true));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ RemoteInput remoteInput =
+ new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
+ RemoteInput freeformRemoteInput =
+ new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
+
+ Notification.Action actionWithFreeformRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(remoteInput)
+ .addRemoteInput(freeformRemoteInput)
+ .build();
+
+ Notification.Action actionWithoutFreeformRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 2", intent)
+ .addRemoteInput(remoteInput)
+ .build();
+
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(actionWithoutFreeformRemoteInput)
+ .addAction(actionWithFreeformRemoteInput)
+ .build();
+
+ Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+ notification.findRemoteInputActionPair(true);
+
+ assertNotNull(remoteInputActionPair);
+ Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first);
+ Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
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/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/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 0bc70857a113..3ee20ea95ee5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -321,4 +321,21 @@
<!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
<dimen name="floating_dismiss_circle_small">120dp</dimen>
+
+ <!-- The thickness of shadows of a window that has focus in DIP. -->
+ <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen>
+
+ <!-- The thickness of shadows of a window that doesn't have focus in DIP. -->
+ <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen>
+
+ <!-- Height of button (32dp) + 2 * margin (5dp each). -->
+ <dimen name="freeform_decor_caption_height">42dp</dimen>
+
+ <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
+ <dimen name="freeform_decor_caption_width">216dp</dimen>
+
+ <dimen name="freeform_resize_handle">30dp</dimen>
+
+ <dimen name="freeform_resize_corner">44dp</dimen>
+
</resources>
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 87700ee4fb50..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
@@ -21,7 +21,6 @@ import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
@@ -43,22 +42,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
- // The thickness of shadows of a window that has focus in DIP.
- private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
- // The thickness of shadows of a window that doesn't have focus in DIP.
- private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
-
- // Height of button (32dp) + 2 * margin (5dp each)
- private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
- // Width of buttons (64dp) + handle (128dp) + padding (24dp total)
- private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216;
- private static final int RESIZE_HANDLE_IN_DIP = 30;
- private static final int RESIZE_CORNER_IN_DIP = 44;
-
- private static final Rect EMPTY_OUTSET = new Rect();
- private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
- RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
-
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -69,6 +52,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private DragResizeInputListener mDragResizeListener;
+ private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
@@ -114,19 +98,32 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final int shadowRadiusDp = taskInfo.isFocused
- ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
- final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
- final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
+ final int shadowRadiusID = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp,
- startT, finishT, wct, mResult);
+
+ int outsetLeftId = R.dimen.freeform_resize_handle;
+ int outsetTopId = R.dimen.freeform_resize_handle;
+ 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;
+ mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = shadowRadiusID;
+ if (isDragResizeable) {
+ mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
+ }
+ relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
mTaskOrganizer.applyTransaction(wct);
@@ -167,10 +164,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
-
+ int resize_handle = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ int resize_corner = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_corner);
mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP),
- (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop);
+ mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
}
/**
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 bf863ea2c7ab..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
@@ -19,11 +19,11 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.InsetsState;
import android.view.LayoutInflater;
@@ -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();
@@ -142,15 +142,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
- void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- float captionWidthDp, Rect outsetsDp, float shadowRadiusDp,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+ void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
+ RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
- if (taskInfo != null) {
- mTaskInfo = taskInfo;
+ if (params.mRunningTaskInfo != null) {
+ mTaskInfo = params.mRunningTaskInfo;
}
if (!mTaskInfo.isVisible) {
@@ -159,7 +158,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
- if (rootView == null && layoutResId == 0) {
+ if (rootView == null && params.mLayoutResId == 0) {
throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
}
@@ -176,15 +175,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
- if (layoutResId != 0) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ if (params.mLayoutResId != 0) {
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId , null);
}
// DecorationContainerSurface
@@ -200,18 +199,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
- final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+ final Resources resources = mDecorWindowContext.getResources();
+ final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
- + (int) (outsetsDp.right * outResult.mDensity)
+ + loadDimensionPixelSize(resources, params.mOutsetRightId)
- decorContainerOffsetX;
outResult.mHeight = taskBounds.height()
- + (int) (outsetsDp.bottom * outResult.mDensity)
+ + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- decorContainerOffsetY;
startT.setPosition(
mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
+ .setWindowCrop(mDecorationContainerSurface,
+ outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
.setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
.show(mDecorationContainerSurface);
@@ -226,12 +226,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- float shadowRadius = outResult.mDensity * shadowRadiusDp;
+ 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;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
+ taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -248,8 +249,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
- final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity);
+ 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
@@ -289,8 +291,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos;
- wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + captionHeight - captionYPos;
+ wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
+ CAPTION_INSETS_TYPES);
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -365,34 +369,67 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
+ private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ private static float loadDimension(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimension(resourceId);
+ }
+
+ static class RelayoutParams{
+ RunningTaskInfo mRunningTaskInfo;
+ int mLayoutResId;
+ int mCaptionHeightId;
+ int mCaptionWidthId;
+ int mShadowRadiusId;
+
+ int mOutsetTopId;
+ int mOutsetBottomId;
+ int mOutsetLeftId;
+ int mOutsetRightId;
+
+ void setOutsets(int leftId, int topId, int rightId, int bottomId) {
+ mOutsetLeftId = leftId;
+ mOutsetTopId = topId;
+ mOutsetRightId = rightId;
+ mOutsetBottomId = bottomId;
+ }
+
+ void reset() {
+ mLayoutResId = Resources.ID_NULL;
+ mCaptionHeightId = Resources.ID_NULL;
+ mCaptionWidthId = Resources.ID_NULL;
+ mShadowRadiusId = Resources.ID_NULL;
+
+ mOutsetTopId = Resources.ID_NULL;
+ mOutsetBottomId = Resources.ID_NULL;
+ mOutsetLeftId = Resources.ID_NULL;
+ mOutsetRightId = Resources.ID_NULL;
+ }
+ }
+
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mWidth;
int mHeight;
- float mDensity;
T mRootView;
void reset() {
mWidth = 0;
mHeight = 0;
- mDensity = 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);
- }
- }
-
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/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fa62b9c00fc7..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,11 +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.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;
@@ -76,13 +78,9 @@ import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class WindowDecorationTests extends ShellTestCase {
- private static final int CAPTION_HEIGHT_DP = 32;
- private static final int CAPTION_WIDTH_DP = 216;
- private static final int SHADOW_RADIUS_DP = 5;
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
- private final Rect mOutsetsDp = new Rect();
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -104,6 +102,7 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
@@ -147,7 +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;
- mOutsetsDp.set(10, 20, 30, 40);
+ 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);
@@ -197,8 +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;
- mOutsetsDp.set(10, 20, 30, 40);
-
+ 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);
@@ -226,16 +232,17 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
+
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
- && lp.width == 300
+ && 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, 364),
+ new Rect(100, 300, 400, 332),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -248,7 +255,6 @@ public class WindowDecorationTests extends ShellTestCase {
assertEquals(380, mRelayoutResult.mWidth);
assertEquals(220, mRelayoutResult.mHeight);
- assertEquals(2, mRelayoutResult.mDensity, 0.f);
}
@Test
@@ -287,7 +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;
- mOutsetsDp.set(10, 20, 30, 40);
+ 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);
@@ -358,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))),
@@ -410,9 +421,13 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
- mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
+ mRelayoutParams.mLayoutResId = 0;
+ 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/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/MediaCrypto.java b/media/java/android/media/MediaCrypto.java
index 889a5f7efbb2..1930262ea253 100644
--- a/media/java/android/media/MediaCrypto.java
+++ b/media/java/android/media/MediaCrypto.java
@@ -75,14 +75,17 @@ public final class MediaCrypto {
public final native boolean requiresSecureDecoderComponent(@NonNull String mime);
/**
- * Associate a MediaDrm session with this MediaCrypto instance. The
- * MediaDrm session is used to securely load decryption keys for a
- * crypto scheme. The crypto keys loaded through the MediaDrm session
+ * Associate a new MediaDrm session with this MediaCrypto instance.
+ *
+ * <p>The MediaDrm session is used to securely load decryption keys for a
+ * crypto scheme. The crypto keys loaded through the MediaDrm session
* may be selected for use during the decryption operation performed
* by {@link android.media.MediaCodec#queueSecureInputBuffer} by specifying
- * their key ids in the {@link android.media.MediaCodec.CryptoInfo#key} field.
- * @param sessionId the MediaDrm sessionId to associate with this
- * MediaCrypto instance
+ * their key IDs in the {@link android.media.MediaCodec.CryptoInfo#key} field.
+ *
+ * @param sessionId The MediaDrm sessionId to associate with this MediaCrypto
+ * instance. The session's scheme must match the scheme UUID used when
+ * constructing this MediaCrypto instance.
* @throws MediaCryptoException on failure to set the sessionId
*/
public final native void setMediaDrmSession(@NonNull byte[] sessionId)
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index fc753da19394..6990ad0fbd7d 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -10,6 +10,7 @@ package {
android_app {
name: "CarrierDefaultApp",
srcs: ["src/**/*.java"],
+ libs: ["SliceStore"],
platform_apis: true,
certificate: "platform",
}
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 632dfb397d51..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"
@@ -71,5 +72,22 @@
<data android:host="*" />
</intent-filter>
</activity-alias>
+
+ <receiver android:name="com.android.carrierdefaultapp.SliceStoreBroadcastReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.phone.slicestore.action.START_SLICE_STORE" />
+ <action android:name="com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT" />
+ <action android:name="com.android.phone.slicestore.action.NOTIFICATION_CANCELED" />
+ </intent-filter>
+ </receiver>
+ <activity android:name="com.android.carrierdefaultapp.SliceStoreActivity"
+ android:label="@string/slice_store_label"
+ android:exported="true"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml b/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml
new file mode 100644
index 000000000000..ad8a21c7abb6
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/drawable/ic_network_boost.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M3,17V15H8Q8,15 8,15Q8,15 8,15V13Q8,13 8,13Q8,13 8,13H3V7H10V9H5V11H8Q8.825,11 9.413,11.587Q10,12.175 10,13V15Q10,15.825 9.413,16.413Q8.825,17 8,17ZM21,11V15Q21,15.825 20.413,16.413Q19.825,17 19,17H14Q13.175,17 12.588,16.413Q12,15.825 12,15V9Q12,8.175 12.588,7.587Q13.175,7 14,7H19Q19.825,7 20.413,7.587Q21,8.175 21,9H14Q14,9 14,9Q14,9 14,9V15Q14,15 14,15Q14,15 14,15H19Q19,15 19,15Q19,15 19,15V13H16.5V11Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 65a7cecca20f..ce88a401d18e 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -13,4 +13,18 @@
<string name="ssl_error_warning">The network you&#8217;re trying to join has security issues.</string>
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
<string name="ssl_error_continue">Continue anyway via browser</string>
+
+ <!-- Telephony notification channel name for network boost notifications. -->
+ <string name="network_boost_notification_channel">Network Boost</string>
+ <!-- Notification title text for the network boost notification. -->
+ <string name="network_boost_notification_title">%s recommends a data boost</string>
+ <!-- Notification detail text for the network boost notification. -->
+ <string name="network_boost_notification_detail">Buy a network boost for better performance</string>
+ <!-- Notification button text to cancel the network boost notification. -->
+ <string name="network_boost_notification_button_not_now">Not now</string>
+ <!-- Notification button text to manage the network boost notification. -->
+ <string name="network_boost_notification_button_manage">Manage</string>
+
+ <!-- Label to display when the slice store opens. -->
+ <string name="slice_store_label">Purchase a network boost.</string>
</resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
new file mode 100644
index 000000000000..24cb5f948474
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -0,0 +1,122 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.webkit.WebView;
+
+import com.android.phone.slicestore.SliceStore;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Activity that launches when the user clicks on the network boost notification.
+ */
+public class SliceStoreActivity extends Activity {
+ private static final String TAG = "SliceStoreActivity";
+
+ private URL mUrl;
+ private WebView mWebView;
+ private int mPhoneId;
+ private int mSubId;
+ private @TelephonyManager.PremiumCapability int mCapability;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
+ SubscriptionManager.INVALID_PHONE_INDEX);
+ mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
+ SliceStore.PREMIUM_CAPABILITY_INVALID);
+ mUrl = getUrl();
+ logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability="
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+ + ", mUrl=" + mUrl);
+ getApplicationContext().getSystemService(NotificationManager.class)
+ .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+ if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
+ loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponse(
+ intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
+ finishAndRemoveTask();
+ return;
+ }
+ if (mUrl == null) {
+ loge("Unable to create a URL from carrier configs.");
+ SliceStoreBroadcastReceiver.sendSliceStoreResponse(
+ intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
+ finishAndRemoveTask();
+ return;
+ }
+ if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
+ loge("Unable to start SliceStore on the non-default data subscription: " + mSubId);
+ SliceStoreBroadcastReceiver.sendSliceStoreResponse(
+ intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+ finishAndRemoveTask();
+ return;
+ }
+
+ SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
+
+ mWebView = new WebView(getApplicationContext());
+ setContentView(mWebView);
+ mWebView.loadUrl(mUrl.toString());
+ // TODO(b/245882601): Get back response from WebView
+ }
+
+ @Override
+ protected void onDestroy() {
+ logd("onDestroy: User canceled the purchase by closing the application.");
+ SliceStoreBroadcastReceiver.sendSliceStoreResponse(
+ getIntent(), SliceStore.EXTRA_INTENT_CANCELED);
+ SliceStoreBroadcastReceiver.removeSliceStoreActivity(mCapability);
+ super.onDestroy();
+ }
+
+ private @Nullable URL getUrl() {
+ String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
+ .getConfigForSubId(mSubId).getString(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+ try {
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ loge("Invalid URL: " + url);
+ }
+ return null;
+ }
+
+ private static void logd(@NonNull String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void loge(@NonNull String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
new file mode 100644
index 000000000000..7eb851dcdd58
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
@@ -0,0 +1,315 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.WebView;
+
+import com.android.phone.slicestore.SliceStore;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
+ * SliceStore in the phone process to start the SliceStore application. It displays the network
+ * boost notification to the user and will start the {@link SliceStoreActivity} to display the
+ * {@link WebView} to purchase network boosts from the user's carrier.
+ */
+public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
+ private static final String TAG = "SliceStoreBroadcastReceiver";
+
+ /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
+ private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
+ new HashMap<>();
+
+ /** Channel ID for the network boost notification. */
+ private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
+ /** Tag for the network boost notification. */
+ public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification";
+ /** Action for when the user clicks the "Not now" button on the network boost notification. */
+ private static final String ACTION_NOTIFICATION_CANCELED =
+ "com.android.phone.slicestore.action.NOTIFICATION_CANCELED";
+
+ /**
+ * Create a weak reference to {@link SliceStoreActivity}. The reference will be removed when
+ * {@link SliceStoreActivity#onDestroy()} is called.
+ *
+ * @param capability The premium capability requested.
+ * @param sliceStoreActivity The instance of SliceStoreActivity.
+ */
+ public static void updateSliceStoreActivity(@TelephonyManager.PremiumCapability int capability,
+ @NonNull SliceStoreActivity sliceStoreActivity) {
+ sSliceStoreActivities.put(capability, new WeakReference<>(sliceStoreActivity));
+ }
+
+ /**
+ * Remove the weak reference to {@link SliceStoreActivity} when
+ * {@link SliceStoreActivity#onDestroy()} is called.
+ *
+ * @param capability The premium capability requested.
+ */
+ public static void removeSliceStoreActivity(
+ @TelephonyManager.PremiumCapability int capability) {
+ sSliceStoreActivities.remove(capability);
+ }
+
+ /**
+ * Send the PendingIntent containing the corresponding SliceStore response.
+ *
+ * @param intent The Intent containing the PendingIntent extra.
+ * @param extra The extra to get the PendingIntent to send.
+ */
+ public static void sendSliceStoreResponse(@NonNull Intent intent, @NonNull String extra) {
+ PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+ if (pendingIntent == null) {
+ loge("PendingIntent does not exist for extra: " + extra);
+ return;
+ }
+ try {
+ pendingIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+ }
+ }
+
+ /**
+ * Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
+ * This checks that all necessary extras exist and that the values are valid.
+ *
+ * @param intent The intent to check
+ * @return {@code true} if the intent is valid and {@code false} otherwise.
+ */
+ public static boolean isIntentValid(@NonNull Intent intent) {
+ int phoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
+ SubscriptionManager.INVALID_PHONE_INDEX);
+ if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+ loge("isIntentValid: invalid phone index: " + phoneId);
+ return false;
+ }
+
+ int subId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ loge("isIntentValid: invalid subscription ID: " + subId);
+ return false;
+ }
+
+ int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
+ SliceStore.PREMIUM_CAPABILITY_INVALID);
+ if (capability == SliceStore.PREMIUM_CAPABILITY_INVALID) {
+ loge("isIntentValid: invalid premium capability: " + capability);
+ return false;
+ }
+
+ String appName = intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME);
+ if (TextUtils.isEmpty(appName)) {
+ loge("isIntentValid: empty requesting application name: " + appName);
+ return false;
+ }
+
+ return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
+ && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+ }
+
+ private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
+ String intentType = getPendingIntentType(extra);
+ PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+ if (pendingIntent == null) {
+ loge("isPendingIntentValid: " + intentType + " intent not found.");
+ return false;
+ } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) {
+ return true;
+ }
+ loge("isPendingIntentValid: " + intentType + " intent was created by "
+ + pendingIntent.getCreatorPackage() + " instead of the phone process.");
+ return false;
+ }
+
+ @NonNull private static String getPendingIntentType(@NonNull String extra) {
+ switch (extra) {
+ case SliceStore.EXTRA_INTENT_CANCELED: return "canceled";
+ case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
+ case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
+ case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
+ default: {
+ loge("Unknown pending intent extra: " + extra);
+ return "unknown(" + extra + ")";
+ }
+ }
+ }
+
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ logd("onReceive intent: " + intent.getAction());
+ switch (intent.getAction()) {
+ case SliceStore.ACTION_START_SLICE_STORE:
+ onDisplayBoosterNotification(context, intent);
+ break;
+ case SliceStore.ACTION_SLICE_STORE_RESPONSE_TIMEOUT:
+ onTimeout(context, intent);
+ break;
+ case ACTION_NOTIFICATION_CANCELED:
+ onUserCanceled(context, intent);
+ break;
+ default:
+ loge("Received unknown action: " + intent.getAction());
+ }
+ }
+
+ private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) {
+ if (!isIntentValid(intent)) {
+ sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
+ return;
+ }
+
+ context.getSystemService(NotificationManager.class).createNotificationChannel(
+ new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+ context.getResources().getString(
+ R.string.network_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT));
+
+ Notification notification =
+ new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(String.format(context.getResources().getString(
+ R.string.network_boost_notification_title),
+ intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME)))
+ .setContentText(context.getResources().getString(
+ R.string.network_boost_notification_detail))
+ .setSmallIcon(R.drawable.ic_network_boost)
+ .setContentIntent(createContentIntent(context, intent, 1))
+ .setDeleteIntent(intent.getParcelableExtra(
+ SliceStore.EXTRA_INTENT_CANCELED, PendingIntent.class))
+ // Add an action for the "Not now" button, which has the same behavior as
+ // the user canceling or closing the notification.
+ .addAction(new Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_network_boost),
+ context.getResources().getString(
+ R.string.network_boost_notification_button_not_now),
+ createCanceledIntent(context, intent)).build())
+ // Add an action for the "Manage" button, which has the same behavior as
+ // the user clicking on the notification.
+ .addAction(new Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_network_boost),
+ context.getResources().getString(
+ R.string.network_boost_notification_button_manage),
+ createContentIntent(context, intent, 2)).build())
+ .build();
+
+ int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
+ SliceStore.PREMIUM_CAPABILITY_INVALID);
+ logd("Display the booster notification for capability "
+ + TelephonyManager.convertPremiumCapabilityToString(capability));
+ context.getSystemService(NotificationManager.class).notifyAsUser(
+ NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
+ }
+
+ /**
+ * Create the intent for when the user clicks on the "Manage" button on the network boost
+ * notification or the notification itself. This will open {@link SliceStoreActivity}.
+ *
+ * @param context The Context to create the intent for.
+ * @param intent The source Intent used to launch the SliceStore application.
+ * @param requestCode The request code for the PendingIntent.
+ *
+ * @return The intent to start {@link SliceStoreActivity}.
+ */
+ @NonNull private PendingIntent createContentIntent(@NonNull Context context,
+ @NonNull Intent intent, int requestCode) {
+ Intent i = new Intent(context, SliceStoreActivity.class);
+ i.setComponent(ComponentName.unflattenFromString(
+ "com.android.carrierdefaultapp/.SliceStoreActivity"));
+ i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
+ | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ i.putExtras(intent);
+ return PendingIntent.getActivityAsUser(context, requestCode, i,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, null /* options */,
+ UserHandle.CURRENT);
+ }
+
+ /**
+ * Create the canceled intent for when the user clicks the "Not now" button on the network boost
+ * notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same function
+ * as if the user had canceled or removed the notification.
+ *
+ * @param context The Context to create the intent for.
+ * @param intent The source Intent used to launch the SliceStore application.
+ *
+ * @return The canceled intent.
+ */
+ @NonNull private PendingIntent createCanceledIntent(@NonNull Context context,
+ @NonNull Intent intent) {
+ Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
+ i.setComponent(ComponentName.unflattenFromString(
+ "com.android.carrierdefaultapp/.SliceStoreBroadcastReceiver"));
+ i.putExtras(intent);
+ return PendingIntent.getBroadcast(context, 0, i,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+ }
+
+ private void onTimeout(@NonNull Context context, @NonNull Intent intent) {
+ int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
+ SliceStore.PREMIUM_CAPABILITY_INVALID);
+ logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
+ + " timed out.");
+ if (sSliceStoreActivities.get(capability) == null) {
+ // Notification is still active
+ logd("Closing booster notification since the user did not respond in time.");
+ context.getSystemService(NotificationManager.class).cancelAsUser(
+ NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ } else {
+ // Notification was dismissed but SliceStoreActivity is still active
+ logd("Closing SliceStore WebView since the user did not complete the purchase "
+ + "in time.");
+ sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
+ // TODO: Display a toast to indicate timeout for better UX?
+ }
+ }
+
+ private void onUserCanceled(@NonNull Context context, @NonNull Intent intent) {
+ int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
+ SliceStore.PREMIUM_CAPABILITY_INVALID);
+ logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+ context.getSystemService(NotificationManager.class)
+ .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_CANCELED);
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 2c24bf14117a..92ce77282cb9 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -1,4 +1,5 @@
-<resources>
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">CredentialManager</string>
<string name="string_cancel">Cancel</string>
<string name="string_continue">Continue</string>
@@ -12,4 +13,7 @@
<string name="choose_create_option_title">Create a passkey at</string>
<string name="choose_sign_in_title">Use saved sign in</string>
<string name="create_passkey_at">Create passkey at</string>
+ <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoName">%1$s</xliff:g> for all your sign-ins?</string>
+ <string name="set_as_default">Set as default</string>
+ <string name="use_once">Use once</string>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 489cc2791370..ec0c5b708abe 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -21,6 +21,7 @@ import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
import android.content.Intent
+import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
@@ -60,7 +61,7 @@ class CredentialManagerRepo(
) ?: testProviderList()
resultReceiver = intent.getParcelableExtra(
- RequestInfo.EXTRA_RESULT_RECEIVER,
+ Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
}
@@ -118,46 +119,42 @@ class CredentialManagerRepo(
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testProviderList(): List<ProviderData> {
return listOf(
- ProviderData(
+ ProviderData.Builder(
"com.google",
- listOf<Entry>(
- newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett",
- "20 passwords and 7 passkeys saved"),
- newEntry(2, "elisa.work@google.com", "Elisa Backett Work",
- "20 passwords and 7 passkeys saved"),
- ),
- listOf<Entry>(
- newEntry(3, "Go to Settings", "",
- "20 passwords and 7 passkeys saved"),
- newEntry(4, "Switch Account", "",
- "20 passwords and 7 passkeys saved"),
- ),
- null
- ),
- ProviderData(
+ "Google Password Manager",
+ Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+ .setCredentialEntries(
+ listOf<Entry>(
+ newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett",
+ "20 passwords and 7 passkeys saved"),
+ newEntry(2, "elisa.work@google.com", "Elisa Backett Work",
+ "20 passwords and 7 passkeys saved"),
+ )
+ ).setActionChips(
+ listOf<Entry>(
+ newEntry(3, "Go to Settings", "",
+ "20 passwords and 7 passkeys saved"),
+ newEntry(4, "Switch Account", "",
+ "20 passwords and 7 passkeys saved"),
+ ),
+ ).build(),
+ ProviderData.Builder(
"com.dashlane",
- listOf<Entry>(
- newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett",
- "20 passwords and 7 passkeys saved"),
- newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work",
- "20 passwords and 7 passkeys saved"),
- ),
- listOf<Entry>(
- newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app",
- "20 passwords and 7 passkeys saved"),
- ),
- null
- ),
- ProviderData(
- "com.lastpass",
- listOf<Entry>(
- newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett",
- "20 passwords and 7 passkeys saved"),
- ),
- listOf<Entry>(),
- null
- )
-
+ "Dashlane",
+ Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+ .setCredentialEntries(
+ listOf<Entry>(
+ newEntry(1, "elisa.beckett@dashlane.com", "Elisa Backett",
+ "20 passwords and 7 passkeys saved"),
+ newEntry(2, "elisa.work@dashlane.com", "Elisa Backett Work",
+ "20 passwords and 7 passkeys saved"),
+ )
+ ).setActionChips(
+ listOf<Entry>(
+ newEntry(3, "Manage Accounts", "Manage your accounts in the dashlane app",
+ "20 passwords and 7 passkeys saved"),
+ ),
+ ).build(),
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index f4d60b5f77cc..82fce9f7a98d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -83,6 +83,8 @@ fun CreatePasskeyScreen(
onOptionSelected = {viewModel.onMoreOptionsRowSelected(it)}
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
+ providerInfo = uiState.selectedProvider!!,
+ onDefaultOrNotSelected = {viewModel.onDefaultOrNotSelected(it)}
)
}
},
@@ -282,10 +284,37 @@ fun MoreOptionsSelectionCard(
@ExperimentalMaterialApi
@Composable
fun MoreOptionsRowIntroCard(
+ providerInfo: ProviderInfo,
+ onDefaultOrNotSelected: (String) -> Unit,
) {
Card(
backgroundColor = lightBackgroundColor,
) {
+ Column() {
+ Text(
+ text = stringResource(R.string.use_provider_for_all_title, providerInfo.name),
+ style = Typography.subtitle1,
+ modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.use_once),
+ onclick = { onDefaultOrNotSelected(providerInfo.name) }
+ )
+ ConfirmButton(
+ stringResource(R.string.set_as_default),
+ onclick = { onDefaultOrNotSelected(providerInfo.name) }
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 40.dp)
+ )
+ }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 3cf81da1fbaa..ff44e2ee1b06 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -112,4 +112,12 @@ class CreatePasskeyViewModel(
CredentialManagerRepo.getInstance().onCancel()
dialogResult.value = DialogResult(ResultState.CANCELED)
}
+
+ fun onDefaultOrNotSelected(providerName: String) {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ selectedProvider = getProviderInfoByName(providerName)
+ )
+ // TODO: implement the if choose as default or not logic later
+ }
}
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/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 332bebffb637..c35fb3b17880 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -26,4 +26,9 @@ android_library {
"androidx.window.extensions",
"androidx.window.sidecar",
],
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
}
diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 27425589c822..0949e1defc2f 100644
--- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
+++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-sdk android:minSdkVersion="21" />
<application>
+ <uses-library android:name="org.apache.http.legacy" android:required="false" />
<uses-library android:name="androidx.window.extensions" android:required="false" />
<uses-library android:name="androidx.window.sidecar" android:required="false" />
</application>
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 811cdd804f8b..68c63dad6726 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -17,6 +17,7 @@
buildscript {
ext {
spa_min_sdk = 21
+ spa_target_sdk = 33
jetpack_compose_version = '1.2.0-alpha04'
jetpack_compose_compiler_version = '1.3.2'
jetpack_compose_material3_version = '1.0.0-alpha06'
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 0a4972fac1bb..f1a24aff4319 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spa.gallery">
+ <uses-sdk android:minSdkVersion="21"/>
+
<application
android:name=".GalleryApplication"
android:icon="@mipmap/ic_launcher"
@@ -32,14 +34,24 @@
</intent-filter>
</activity>
+ <provider
+ android:name=".GalleryEntryProvider"
+ android:authorities="com.android.spa.gallery.provider"
+ android:enabled="true"
+ android:exported="false">
+ </provider>
+
<activity
- android:name=".GalleryDebugActivity"
+ android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
+ android:exported="true">
+ </activity>
+ <activity
+ android:name="com.android.settingslib.spa.framework.debug.DebugActivity"
android:exported="true">
</activity>
-
<provider
- android:name=".GalleryEntryProvider"
- android:authorities="com.android.spa.gallery.provider"
+ android:name="com.android.settingslib.spa.framework.debug.DebugProvider"
+ android:authorities="com.android.spa.gallery.debug"
android:enabled="true"
android:exported="false">
</provider>
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 551a0b107e3a..c1ce7d96702d 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -21,12 +21,12 @@ plugins {
android {
namespace 'com.android.settingslib.spa.gallery'
- compileSdk 33
+ compileSdk spa_target_sdk
defaultConfig {
applicationId "com.android.settingslib.spa.gallery"
minSdk spa_min_sdk
- targetSdk 33
+ targetSdk spa_target_sdk
versionCode 1
versionName "1.0"
}
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/AndroidManifest.xml b/packages/SettingsLib/Spa/spa/AndroidManifest.xml
index 410bcdb36782..62800bd34217 100644
--- a/packages/SettingsLib/Spa/spa/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/spa/AndroidManifest.xml
@@ -14,4 +14,7 @@
limitations under the License.
-->
-<manifest package="com.android.settingslib.spa" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spa">
+ <uses-sdk android:minSdkVersion="21"/>
+</manifest>
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 7e05e75804a7..c5874113ef32 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -21,11 +21,11 @@ plugins {
android {
namespace 'com.android.settingslib.spa'
- compileSdk 33
+ compileSdk spa_target_sdk
defaultConfig {
minSdk spa_min_sdk
- targetSdk 33
+ targetSdk spa_target_sdk
}
sourceSets {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index 532f63b67c5d..d6317085e4f9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -16,21 +16,22 @@
package com.android.settingslib.spa.framework
-import android.content.ComponentName
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.Intent
-import android.content.Intent.URI_INTENT_SCHEME
import android.content.UriMatcher
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.addUri
+import com.android.settingslib.spa.framework.common.getColumns
private const val TAG = "EntryProvider"
@@ -39,117 +40,15 @@ private const val TAG = "EntryProvider"
* One can query the provider result by:
* $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
* For gallery, AuthorityPath = com.android.spa.gallery.provider
- * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider
+ * For Settings, AuthorityPath = com.android.settings.spa.provider
* Some examples:
- * $ adb shell content query --uri content://<AuthorityPath>/page_debug
- * $ adb shell content query --uri content://<AuthorityPath>/entry_debug
- * $ adb shell content query --uri content://<AuthorityPath>/page_info
- * $ adb shell content query --uri content://<AuthorityPath>/entry_info
* $ adb shell content query --uri content://<AuthorityPath>/search_sitemap
* $ adb shell content query --uri content://<AuthorityPath>/search_static
* $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
*/
open class EntryProvider : ContentProvider() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
-
- /**
- * Enum to define all column names in provider.
- */
- enum class ColumnEnum(val id: String) {
- // Columns related to page
- PAGE_ID("pageId"),
- PAGE_NAME("pageName"),
- PAGE_ROUTE("pageRoute"),
- PAGE_INTENT_URI("pageIntent"),
- PAGE_ENTRY_COUNT("entryCount"),
- HAS_RUNTIME_PARAM("hasRuntimeParam"),
- PAGE_START_ADB("pageStartAdb"),
-
- // Columns related to entry
- ENTRY_ID("entryId"),
- ENTRY_NAME("entryName"),
- ENTRY_ROUTE("entryRoute"),
- ENTRY_INTENT_URI("entryIntent"),
- ENTRY_HIERARCHY_PATH("entryPath"),
- ENTRY_START_ADB("entryStartAdb"),
-
- // Columns related to search
- ENTRY_TITLE("entryTitle"),
- ENTRY_SEARCH_KEYWORD("entrySearchKw"),
- }
-
- /**
- * Enum to define all queries supported in the provider.
- */
- enum class QueryEnum(
- val queryPath: String,
- val queryMatchCode: Int,
- val columnNames: List<ColumnEnum>
- ) {
- // For debug
- PAGE_DEBUG_QUERY(
- "page_debug", 1,
- listOf(ColumnEnum.PAGE_START_ADB)
- ),
- ENTRY_DEBUG_QUERY(
- "entry_debug", 2,
- listOf(ColumnEnum.ENTRY_START_ADB)
- ),
-
- // page related queries.
- PAGE_INFO_QUERY(
- "page_info", 100,
- listOf(
- ColumnEnum.PAGE_ID,
- ColumnEnum.PAGE_NAME,
- ColumnEnum.PAGE_ROUTE,
- ColumnEnum.PAGE_INTENT_URI,
- ColumnEnum.PAGE_ENTRY_COUNT,
- ColumnEnum.HAS_RUNTIME_PARAM,
- )
- ),
-
- // entry related queries
- ENTRY_INFO_QUERY(
- "entry_info", 200,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_NAME,
- ColumnEnum.ENTRY_ROUTE,
- ColumnEnum.ENTRY_INTENT_URI,
- )
- ),
-
- // Search related queries
- SEARCH_SITEMAP_QUERY(
- "search_sitemap", 300,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_HIERARCHY_PATH,
- )
- ),
- SEARCH_STATIC_DATA_QUERY(
- "search_static", 301,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_TITLE,
- ColumnEnum.ENTRY_SEARCH_KEYWORD,
- )
- ),
- SEARCH_DYNAMIC_DATA_QUERY(
- "search_dynamic", 302,
- listOf(
- ColumnEnum.ENTRY_ID,
- ColumnEnum.ENTRY_TITLE,
- ColumnEnum.ENTRY_SEARCH_KEYWORD,
- )
- ),
- }
-
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
- private fun addUri(authority: String, query: QueryEnum) {
- uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode)
- }
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
TODO("Implement this to handle requests to delete one or more rows")
@@ -182,13 +81,9 @@ open class EntryProvider : ContentProvider() {
override fun attachInfo(context: Context?, info: ProviderInfo?) {
if (info != null) {
- addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY)
- addUri(info.authority, QueryEnum.ENTRY_DEBUG_QUERY)
- addUri(info.authority, QueryEnum.PAGE_INFO_QUERY)
- addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY)
- addUri(info.authority, QueryEnum.SEARCH_SITEMAP_QUERY)
- addUri(info.authority, QueryEnum.SEARCH_STATIC_DATA_QUERY)
- addUri(info.authority, QueryEnum.SEARCH_DYNAMIC_DATA_QUERY)
+ QueryEnum.SEARCH_SITEMAP_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
}
super.attachInfo(context, info)
}
@@ -202,10 +97,6 @@ open class EntryProvider : ContentProvider() {
): Cursor? {
return try {
when (uriMatcher.match(uri)) {
- QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
- QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug()
- QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
- QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
QueryEnum.SEARCH_SITEMAP_QUERY.queryMatchCode -> querySearchSitemap()
QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
@@ -219,73 +110,18 @@ open class EntryProvider : ContentProvider() {
}
}
- private fun queryPageDebug(): Cursor {
- val entryRepository by spaEnvironment.entryRepository
- val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
- for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
- val command = createBrowsePageAdbCommand(pageWithEntry.page)
- if (command != null) {
- cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
- }
- }
- return cursor
- }
-
- private fun queryEntryDebug(): Cursor {
- val entryRepository by spaEnvironment.entryRepository
- val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
- for (entry in entryRepository.getAllEntries()) {
- val command = createBrowsePageAdbCommand(entry.containerPage(), entry.id)
- if (command != null) {
- cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
- }
- }
- return cursor
- }
-
- private fun queryPageInfo(): Cursor {
- val entryRepository by spaEnvironment.entryRepository
- val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
- for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
- val page = pageWithEntry.page
- cursor.newRow()
- .add(ColumnEnum.PAGE_ID.id, page.id)
- .add(ColumnEnum.PAGE_NAME.id, page.displayName)
- .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
- .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
- .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
- .add(
- ColumnEnum.PAGE_INTENT_URI.id,
- createBrowsePageIntent(page).toUri(URI_INTENT_SCHEME)
- )
- }
- return cursor
- }
-
- private fun queryEntryInfo(): Cursor {
- val entryRepository by spaEnvironment.entryRepository
- val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
- for (entry in entryRepository.getAllEntries()) {
- cursor.newRow()
- .add(ColumnEnum.ENTRY_ID.id, entry.id)
- .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
- .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
- .add(
- ColumnEnum.ENTRY_INTENT_URI.id,
- createBrowsePageIntent(entry.containerPage(), entry.id).toUri(URI_INTENT_SCHEME)
- )
- }
- return cursor
- }
-
private fun querySearchSitemap(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_SITEMAP_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
if (!entry.isAllowSearch) continue
+ val intent = entry.containerPage()
+ .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+ ?: Intent()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
.add(ColumnEnum.ENTRY_HIERARCHY_PATH.id, entryRepository.getEntryPath(entry.id))
+ .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
}
return cursor
}
@@ -321,54 +157,4 @@ open class EntryProvider : ContentProvider() {
searchData?.keyword ?: emptyList<String>()
)
}
-
- private fun createBrowsePageIntent(page: SettingsPage, entryId: String? = null): Intent {
- if (!isPageBrowsable(page)) return Intent()
- return Intent().setComponent(ComponentName(context!!, spaEnvironment.browseActivityClass!!))
- .apply {
- putExtra(BrowseActivity.KEY_DESTINATION, page.buildRoute())
- if (entryId != null) {
- putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
- }
- }
- }
-
- private fun createBrowsePageAdbCommand(page: SettingsPage, entryId: String? = null): String? {
- if (!isPageBrowsable(page)) return null
- val packageName = context!!.packageName
- val activityName = spaEnvironment.browseActivityClass!!.name.replace(packageName, "")
- val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
- val highlightParam =
- if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
- return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
- }
-
- private fun isPageBrowsable(page: SettingsPage): Boolean {
- return context != null &&
- spaEnvironment.browseActivityClass != null &&
- !page.hasRuntimeParam()
- }
-}
-
-fun EntryProvider.QueryEnum.getColumns(): Array<String> {
- return columnNames.map { it.id }.toTypedArray()
-}
-
-fun EntryProvider.QueryEnum.getIndex(name: EntryProvider.ColumnEnum): Int {
- return columnNames.indexOf(name)
-}
-
-fun Cursor.getString(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): String {
- return this.getString(query.getIndex(columnName))
-}
-
-fun Cursor.getInt(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): Int {
- return this.getInt(query.getIndex(columnName))
-}
-
-fun Cursor.getBoolean(
- query: EntryProvider.QueryEnum,
- columnName: EntryProvider.ColumnEnum
-): Boolean {
- return this.getInt(query.getIndex(columnName)) == 1
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
new file mode 100644
index 000000000000..0707429505c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.framework.common
+
+import android.content.UriMatcher
+
+/**
+ * Enum to define all column names in provider.
+ */
+enum class ColumnEnum(val id: String) {
+ // Columns related to page
+ PAGE_ID("pageId"),
+ PAGE_NAME("pageName"),
+ PAGE_ROUTE("pageRoute"),
+ PAGE_INTENT_URI("pageIntent"),
+ PAGE_ENTRY_COUNT("entryCount"),
+ HAS_RUNTIME_PARAM("hasRuntimeParam"),
+ PAGE_START_ADB("pageStartAdb"),
+
+ // Columns related to entry
+ ENTRY_ID("entryId"),
+ ENTRY_NAME("entryName"),
+ ENTRY_ROUTE("entryRoute"),
+ ENTRY_INTENT_URI("entryIntent"),
+ ENTRY_HIERARCHY_PATH("entryPath"),
+ ENTRY_START_ADB("entryStartAdb"),
+
+ // Columns related to search
+ ENTRY_TITLE("entryTitle"),
+ ENTRY_SEARCH_KEYWORD("entrySearchKw"),
+}
+
+/**
+ * Enum to define all queries supported in the provider.
+ */
+enum class QueryEnum(
+ val queryPath: String,
+ val queryMatchCode: Int,
+ val columnNames: List<ColumnEnum>
+) {
+ // For debug
+ PAGE_DEBUG_QUERY(
+ "page_debug", 1,
+ listOf(ColumnEnum.PAGE_START_ADB)
+ ),
+ ENTRY_DEBUG_QUERY(
+ "entry_debug", 2,
+ listOf(ColumnEnum.ENTRY_START_ADB)
+ ),
+
+ // page related queries.
+ PAGE_INFO_QUERY(
+ "page_info", 100,
+ listOf(
+ ColumnEnum.PAGE_ID,
+ ColumnEnum.PAGE_NAME,
+ ColumnEnum.PAGE_ROUTE,
+ ColumnEnum.PAGE_INTENT_URI,
+ ColumnEnum.PAGE_ENTRY_COUNT,
+ ColumnEnum.HAS_RUNTIME_PARAM,
+ )
+ ),
+
+ // entry related queries
+ ENTRY_INFO_QUERY(
+ "entry_info", 200,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_NAME,
+ ColumnEnum.ENTRY_ROUTE,
+ ColumnEnum.ENTRY_INTENT_URI,
+ )
+ ),
+
+ // Search related queries
+ SEARCH_SITEMAP_QUERY(
+ "search_sitemap", 300,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_HIERARCHY_PATH,
+ ColumnEnum.ENTRY_INTENT_URI,
+ )
+ ),
+ SEARCH_STATIC_DATA_QUERY(
+ "search_static", 301,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_TITLE,
+ ColumnEnum.ENTRY_SEARCH_KEYWORD,
+ )
+ ),
+ SEARCH_DYNAMIC_DATA_QUERY(
+ "search_dynamic", 302,
+ listOf(
+ ColumnEnum.ENTRY_ID,
+ ColumnEnum.ENTRY_TITLE,
+ ColumnEnum.ENTRY_SEARCH_KEYWORD,
+ )
+ ),
+}
+
+internal fun QueryEnum.getColumns(): Array<String> {
+ return columnNames.map { it.id }.toTypedArray()
+}
+
+internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+ return columnNames.indexOf(name)
+}
+
+internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+ uriMatcher.addURI(authority, queryPath, queryMatchCode)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 8616b9f1ddd3..fb42f01b14f8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -37,7 +37,7 @@ interface EntryData {
}
val LocalEntryDataProvider =
- compositionLocalOf<EntryData> { object : EntryData{} }
+ compositionLocalOf<EntryData> { object : EntryData {} }
/**
* Defines data of a Settings entry.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 8f63c47b1a9b..07df96e778c4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -16,8 +16,13 @@
package com.android.settingslib.spa.framework.common
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
import android.os.Bundle
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.util.isRuntimeParam
import com.android.settingslib.spa.framework.util.navLink
import com.android.settingslib.spa.framework.util.normalize
@@ -111,6 +116,41 @@ data class SettingsPage(
details = formatDisplayTitle()
)
}
+
+ fun createBrowseIntent(
+ context: Context?,
+ browseActivityClass: Class<out Activity>?,
+ entryId: String? = null
+ ): Intent? {
+ if (!isBrowsable(context, browseActivityClass)) return null
+ return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
+ .apply {
+ putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
+ if (entryId != null) {
+ putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
+ }
+ }
+ }
+
+ fun createBrowseAdbCommand(
+ context: Context?,
+ browseActivityClass: Class<out Activity>?,
+ entryId: String? = null
+ ): String? {
+ if (!isBrowsable(context, browseActivityClass)) return null
+ val packageName = context!!.packageName
+ val activityName = browseActivityClass!!.name.replace(packageName, "")
+ val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
+ val highlightParam =
+ if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
+ return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
+ }
+
+ fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
+ return context != null &&
+ browseActivityClass != null &&
+ !hasRuntimeParam()
+ }
}
fun String.toHashId(): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 6f968180e243..301508074f30 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.debug
-import android.content.Intent
-import android.net.Uri
import android.os.Bundle
-import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
@@ -33,8 +30,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -60,11 +55,10 @@ private const val PARAM_NAME_ENTRY_ID = "eid"
/**
* The Debug Activity to display all Spa Pages & Entries.
* One can open the debug activity by:
- * $ adb shell am start -n <Activity>
- * For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity
- * For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity
+ * $ adb shell am start -n <Package>/com.android.settingslib.spa.framework.debug.DebugActivity
+ * For gallery, Package = com.android.settingslib.spa.gallery
*/
-open class DebugActivity : ComponentActivity() {
+class DebugActivity : ComponentActivity() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
@@ -79,30 +73,6 @@ open class DebugActivity : ComponentActivity() {
}
}
- private fun displayDebugMessage() {
- val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return
-
- try {
- val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY
- contentResolver.query(
- Uri.parse("content://$entryProviderAuthorities/${query.queryPath}"),
- null, null, null
- ).use { cursor ->
- while (cursor != null && cursor.moveToNext()) {
- val route = cursor.getString(query, EntryProvider.ColumnEnum.PAGE_ROUTE)
- val entryCount = cursor.getInt(query, EntryProvider.ColumnEnum.PAGE_ENTRY_COUNT)
- val hasRuntimeParam =
- cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM)
- val message = "Page Info: $route ($entryCount) " +
- (if (hasRuntimeParam) "with" else "no") + "-runtime-params"
- spaEnvironment.logger.message(TAG, message, category = LogCategory.FRAMEWORK)
- }
- }
- } catch (e: Exception) {
- Log.e(TAG, "Provider querying exception:", e)
- }
- }
-
@Composable
private fun MainContent() {
val navController = rememberNavController()
@@ -141,11 +111,6 @@ open class DebugActivity : ComponentActivity() {
override val title = "List All Entries (${allEntry.size})"
override val onClick = navigator(route = ROUTE_All_ENTRIES)
})
- Preference(object : PreferenceModel {
- override val title = "Query EntryProvider"
- override val enabled = isEntryProviderAvailable().toState()
- override val onClick = { displayDebugMessage() }
- })
}
}
@@ -177,6 +142,7 @@ open class DebugActivity : ComponentActivity() {
@Composable
fun OnePage(arguments: Bundle?) {
+ val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
@@ -186,7 +152,9 @@ open class DebugActivity : ComponentActivity() {
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
override val title = "open page"
- override val enabled = isPageClickable(pageWithEntry.page).toState()
+ override val enabled =
+ pageWithEntry.page.isBrowsable(context, spaEnvironment.browseActivityClass)
+ .toState()
override val onClick = openPage(pageWithEntry.page)
})
EntryList(pageWithEntry.entries)
@@ -195,6 +163,7 @@ open class DebugActivity : ComponentActivity() {
@Composable
fun OneEntry(arguments: Bundle?) {
+ val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
val entry = entryRepository.getEntry(id)!!
@@ -202,7 +171,9 @@ open class DebugActivity : ComponentActivity() {
RegularScaffold(title = "Entry - ${entry.displayTitle()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
- override val enabled = isEntryClickable(entry).toState()
+ override val enabled =
+ entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
+ .toState()
override val onClick = openEntry(entry)
})
Text(text = entryContent)
@@ -223,12 +194,10 @@ open class DebugActivity : ComponentActivity() {
@Composable
private fun openPage(page: SettingsPage): (() -> Unit)? {
- if (!isPageClickable(page)) return null
val context = LocalContext.current
+ val intent =
+ page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
val route = page.buildRoute()
- val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
- putExtra(KEY_DESTINATION, route)
- }
return {
spaEnvironment.logger.message(
TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK
@@ -239,13 +208,11 @@ open class DebugActivity : ComponentActivity() {
@Composable
private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
- if (!isEntryClickable(entry)) return null
val context = LocalContext.current
+ val intent = entry.containerPage()
+ .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+ ?: return null
val route = entry.containerPage().buildRoute()
- val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
- putExtra(KEY_DESTINATION, route)
- putExtra(KEY_HIGHLIGHT_ENTRY, entry.id)
- }
return {
spaEnvironment.logger.message(
TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK
@@ -253,17 +220,9 @@ open class DebugActivity : ComponentActivity() {
context.startActivity(intent)
}
}
-
- private fun isEntryProviderAvailable(): Boolean {
- return spaEnvironment.entryProviderAuthorities != null
- }
-
- private fun isPageClickable(page: SettingsPage): Boolean {
- return spaEnvironment.browseActivityClass != null && !page.hasRuntimeParam()
- }
-
- private fun isEntryClickable(entry: SettingsEntry): Boolean {
- return spaEnvironment.browseActivityClass != null &&
- !entry.containerPage().hasRuntimeParam()
- }
}
+
+/**
+ * A blank activity without any page.
+ */
+class BlankActivity : ComponentActivity()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
new file mode 100644
index 000000000000..6c271094de9f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.framework.debug
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.URI_INTENT_SCHEME
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.addUri
+import com.android.settingslib.spa.framework.common.getColumns
+
+private const val TAG = "DebugProvider"
+
+/**
+ * The content provider to return debug data.
+ * One can query the provider result by:
+ * $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
+ * For gallery, AuthorityPath = com.android.spa.gallery.debug
+ * Some examples:
+ * $ adb shell content query --uri content://<AuthorityPath>/page_debug
+ * $ adb shell content query --uri content://<AuthorityPath>/entry_debug
+ * $ adb shell content query --uri content://<AuthorityPath>/page_info
+ * $ adb shell content query --uri content://<AuthorityPath>/entry_info
+ */
+class DebugProvider : ContentProvider() {
+ private val spaEnvironment get() = SpaEnvironmentFactory.instance
+ private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+ TODO("Implement this to handle requests to delete one or more rows")
+ }
+
+ override fun getType(uri: Uri): String? {
+ TODO(
+ "Implement this to handle requests for the MIME type of the data" +
+ "at the given URI"
+ )
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ TODO("Implement this to handle requests to insert a new row.")
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int {
+ TODO("Implement this to handle requests to update one or more rows.")
+ }
+
+ override fun onCreate(): Boolean {
+ Log.d(TAG, "onCreate")
+ return true
+ }
+
+ override fun attachInfo(context: Context?, info: ProviderInfo?) {
+ if (info != null) {
+ QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority)
+ QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority)
+ }
+ super.attachInfo(context, info)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ return try {
+ when (uriMatcher.match(uri)) {
+ QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
+ QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug()
+ QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+ QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
+ else -> throw UnsupportedOperationException("Unknown Uri $uri")
+ }
+ } catch (e: UnsupportedOperationException) {
+ throw e
+ } catch (e: Exception) {
+ Log.e(TAG, "Provider querying exception:", e)
+ null
+ }
+ }
+
+ private fun queryPageDebug(): Cursor {
+ val entryRepository by spaEnvironment.entryRepository
+ val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val command = pageWithEntry.page.createBrowseAdbCommand(
+ context,
+ spaEnvironment.browseActivityClass
+ )
+ if (command != null) {
+ cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
+ }
+ }
+ return cursor
+ }
+
+ private fun queryEntryDebug(): Cursor {
+ val entryRepository by spaEnvironment.entryRepository
+ val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
+ for (entry in entryRepository.getAllEntries()) {
+ val command = entry.containerPage()
+ .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
+ if (command != null) {
+ cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
+ }
+ }
+ return cursor
+ }
+
+ private fun queryPageInfo(): Cursor {
+ val entryRepository by spaEnvironment.entryRepository
+ val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val page = pageWithEntry.page
+ val intent =
+ page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
+ cursor.newRow()
+ .add(ColumnEnum.PAGE_ID.id, page.id)
+ .add(ColumnEnum.PAGE_NAME.id, page.displayName)
+ .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
+ .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
+ .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+ .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
+ }
+ return cursor
+ }
+
+ private fun queryEntryInfo(): Cursor {
+ val entryRepository by spaEnvironment.entryRepository
+ val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
+ for (entry in entryRepository.getAllEntries()) {
+ val intent = entry.containerPage()
+ .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+ ?: Intent()
+ cursor.newRow()
+ .add(ColumnEnum.ENTRY_ID.id, entry.id)
+ .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+ .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
+ .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
+ }
+ return cursor
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 3fa8c658e198..52c489324699 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -44,3 +44,6 @@ internal fun materialColorScheme(isDarkTheme: Boolean): ColorScheme {
val ColorScheme.divider: Color
get() = onSurface.copy(SettingsOpacity.Divider)
+
+val ColorScheme.surfaceTone: Color
+ get() = primary.copy(SettingsOpacity.SurfaceTone)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 11af6ce0beab..69ddf01b6170 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -20,4 +20,5 @@ object SettingsOpacity {
const val Full = 1f
const val Disabled = 0.38f
const val Divider = 0.2f
+ const val SurfaceTone = 0.14f
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
index 6c7432eee16a..8d0a35c371e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
@@ -23,7 +23,7 @@ import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@Composable
-fun LogEntryEvent(): (event: LogEvent) -> Unit {
+fun logEntryEvent(): (event: LogEvent) -> Unit {
val entryId = LocalEntryDataProvider.current.entryId ?: return {}
return {
SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
@@ -31,9 +31,9 @@ fun LogEntryEvent(): (event: LogEvent) -> Unit {
}
@Composable
-fun WrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
+fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
if (onClick == null) return null
- val logEvent = LogEntryEvent()
+ val logEvent = logEntryEvent()
return {
logEvent(LogEvent.ENTRY_CLICK)
onClick()
@@ -41,9 +41,9 @@ fun WrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
}
@Composable
-fun WrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? {
+fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? {
if (onSwitch == null) return null
- val logEvent = LogEntryEvent()
+ val logEvent = logEntryEvent()
return {
val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
logEvent(event)
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/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index 3e04b16f08cf..db95e23bb52b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -28,7 +28,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.widget.util.EntryHighlight
@Composable
fun MainSwitchPreference(model: SwitchPreferenceModel) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 7c0116a582f6..6ebe6bb9dfa3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -26,9 +26,9 @@ import com.android.settingslib.spa.framework.common.EntryMacro
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.framework.util.WrapOnClickWithLog
-import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.wrapOnClickWithLog
import com.android.settingslib.spa.widget.ui.createSettingsIcon
+import com.android.settingslib.spa.widget.util.EntryHighlight
data class SimplePreferenceMacro(
val title: String,
@@ -107,7 +107,7 @@ fun Preference(
model: PreferenceModel,
singleLineSummary: Boolean = false,
) {
- val onClickWithLog = WrapOnClickWithLog(model.onClick)
+ val onClickWithLog = wrapOnClickWithLog(model.onClick)
val modifier = remember(model.enabled.value) {
if (onClickWithLog != null) {
Modifier.clickable(
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/preference/SliderPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
index 7bca38fdb48f..4ee2af0fa1b7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
@@ -31,8 +31,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.SettingsSlider
+import com.android.settingslib.spa.widget.util.EntryHighlight
/**
* The widget model for [SliderPreference] widget.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 592a99f4fa5c..2d606193872d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -31,9 +31,9 @@ import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.WrapOnSwitchWithLog
-import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
import com.android.settingslib.spa.widget.ui.SettingsSwitch
+import com.android.settingslib.spa.widget.util.EntryHighlight
/**
* The widget model for [SwitchPreference] widget.
@@ -104,7 +104,7 @@ internal fun InternalSwitchPreference(
) {
val checkedValue = checked.value
val indication = LocalIndication.current
- val onChangeWithLog = WrapOnSwitchWithLog(onCheckedChange)
+ val onChangeWithLog = wrapOnSwitchWithLog(onCheckedChange)
val modifier = remember(checkedValue, changeable.value) {
if (checkedValue != null && onChangeWithLog != null) {
Modifier.toggleable(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 63de2c821154..fbfcaaa28047 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -17,7 +17,7 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.widget.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.SettingsSwitch
@Composable
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/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
index d8455e46ec4a..48fec3bd8ad7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -16,13 +16,16 @@
package com.android.settingslib.spa.widget.ui
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.theme.surfaceTone
import kotlin.math.roundToInt
@Composable
@@ -45,5 +48,8 @@ fun SettingsSlider(
valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
steps = if (showSteps) (valueRange.count() - 2) else 0,
onValueChangeFinished = onValueChangeFinished,
+ colors = SliderDefaults.colors(
+ inactiveTrackColor = MaterialTheme.colorScheme.surfaceTone
+ )
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
index 82ab0be55002..b9690762845e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -20,7 +20,7 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import com.android.settingslib.spa.framework.util.WrapOnSwitchWithLog
+import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -35,7 +35,7 @@ fun SettingsSwitch(
if (checkedValue != null) {
Checkbox(
checked = checkedValue,
- onCheckedChange = WrapOnSwitchWithLog(onCheckedChange),
+ onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
enabled = changeable.value,
)
} else {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
index 8e24ce042d43..652e54de5e39 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework.util
+package com.android.settingslib.spa.widget.util
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 1ce49fa520b9..74910456471e 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -34,4 +34,5 @@ android_test {
"truth-prebuilt",
],
kotlincflags: ["-Xjvm-default=all"],
+ min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
index c224cafa4740..e2db5943ae53 100644
--- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spa.tests">
+ <uses-sdk android:minSdkVersion="21"/>
+
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index f950e01b9740..b43bf1854c26 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -21,11 +21,11 @@ plugins {
android {
namespace 'com.android.settingslib.spa.tests'
- compileSdk 33
+ compileSdk spa_target_sdk
defaultConfig {
minSdk spa_min_sdk
- targetSdk 33
+ targetSdk spa_target_sdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
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/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index 1dc52cbfb4ec..99649263c76a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -3,6 +3,8 @@ package com.android.settingslib.spaprivileged.framework.common
import android.app.admin.DevicePolicyManager
import android.app.usage.StorageStatsManager
import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.os.UserHandle
import android.os.UserManager
/** The [UserManager] instance. */
@@ -13,3 +15,10 @@ val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::cl
/** The [StorageStatsManager] instance. */
val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
+
+/** The [DomainVerificationManager] instance. */
+val Context.domainVerificationManager
+ get() = getSystemService(DomainVerificationManager::class.java)!!
+
+/** Gets a new [Context] for the given [UserHandle]. */
+fun Context.asUser(userHandle: UserHandle): Context = createContextAsUser(userHandle, 0)
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..8acc3e7a1a9a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -715,6 +715,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/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
new file mode 100644
index 000000000000..1db072548a76
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+private const val CLASS_SETTINGS = "android.provider.Settings"
+
+/**
+ * Detects usage of static methods in android.provider.Settings and suggests to use an injected
+ * settings provider instance instead.
+ */
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf(
+ "getFloat",
+ "getInt",
+ "getLong",
+ "getString",
+ "getUriFor",
+ "putFloat",
+ "putInt",
+ "putLong",
+ "putString"
+ )
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+ val className = method.containingClass?.qualifiedName
+ if (
+ className != "$CLASS_SETTINGS.Global" &&
+ className != "$CLASS_SETTINGS.Secure" &&
+ className != "$CLASS_SETTINGS.System"
+ ) {
+ return
+ }
+ if (!evaluator.isStatic(method)) {
+ return
+ }
+
+ val subclassName = className.substring(CLASS_SETTINGS.length + 1)
+
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "`@Inject` a ${subclassName}Settings instead"
+ )
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "StaticSettingsProvider",
+ briefDescription = "Static settings provider usage",
+ explanation =
+ """
+ Static settings provider methods, such as `Settings.Global.putInt()`, should \
+ not be used because they make testing difficult. Instead, use an injected \
+ settings provider. For example, instead of calling `Settings.Secure.getInt()`, \
+ annotate the class constructor with `@Inject` and add `SecureSettings` to the \
+ parameters.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ StaticSettingsProviderDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index cf7c1b5e44a2..3f334c1cdb9c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -36,6 +36,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
+ StaticSettingsProviderDetector.ISSUE
)
override val api: Int
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 486af9dd5d98..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,6 +18,8 @@ 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")
@@ -30,132 +32,8 @@ private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(sourc
*/
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;
@@ -167,23 +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 {
-}
-"""
- ),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 6ae8fd3f25a1..c35ac61a6543 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class BindServiceOnMainThreadDetectorTest : LintDetectorTest() {
+class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 7d422807ae08..376acb56fac9 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
+class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index c468af8d09e0..301c338f9b42 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class NonInjectedMainThreadDetectorTest : LintDetectorTest() {
+class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index c83a35b46ca6..0a74bfcfee57 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class NonInjectedServiceDetectorTest : LintDetectorTest() {
+class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
@Test
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index ebcddebfbc28..9ed7aa029b1d 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index b03a11c4f02f..54cac7b35598 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class SlowUserQueryDetectorTest : LintDetectorTest() {
+class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> =
listOf(
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 fb6537e92d15..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
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class SoftwareBitmapDetectorTest : LintDetectorTest() {
+class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
@@ -54,12 +51,12 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
.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
"""
)
@@ -70,7 +67,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
lint()
.files(
TestFiles.java(
- """
+ """
import android.graphics.Bitmap;
public class TestClass {
@@ -79,8 +76,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
}
}
"""
- )
- .indented(),
+ ),
*stubs
)
.issues(SoftwareBitmapDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
new file mode 100644
index 000000000000..b83ed7067bc3
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
+
+ override fun getDetector(): Detector = StaticSettingsProviderDetector()
+ override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
+
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+ Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:10: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:11: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~
+ src/test/pkg/TestClass.java:12: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:13: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:14: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:15: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:16: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:17: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:18: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:19: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:20: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:21: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:23: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:24: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~
+ src/test/pkg/TestClass.java:25: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:26: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:27: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:28: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:29: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:30: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:31: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:32: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:33: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:34: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:36: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:37: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~
+ src/test/pkg/TestClass.java:38: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:39: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:40: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:41: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:42: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:43: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:44: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:45: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:46: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:47: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ 0 errors, 36 warnings
+ """
+ )
+ }
+
+ private val stubs = androidStubs
+}
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
new file mode 100644
index 000000000000..3f93f075fe8b
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -0,0 +1,48 @@
+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
+ */
+ override fun lint(): TestLintTask =
+ super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+}
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/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index cafaaf854eed..7709f210f22f 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -33,6 +33,7 @@ java_library {
static_libs: [
"androidx.annotation_annotation",
+ "error_prone_annotations",
"PluginCoreLib",
"SystemUIAnimationLib",
],
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 6124e10144f2..6436dcb5f613 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.os.Trace
import android.util.Log
-import com.android.systemui.log.dagger.LogModule
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.util.concurrent.ArrayBlockingQueue
@@ -61,15 +60,18 @@ import kotlin.math.max
* In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
* the first letter of any of the previous.
*
- * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
+ * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI
+ * LogBufferFactory.
*
* @param name The name of this buffer, printed when the buffer is dumped and in some other
* situations.
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
- * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
- * the maximum, it behaves like a ring buffer.
+ * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches the
+ * maximum, it behaves like a ring buffer.
*/
-class LogBuffer @JvmOverloads constructor(
+class LogBuffer
+@JvmOverloads
+constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
@@ -78,7 +80,7 @@ class LogBuffer @JvmOverloads constructor(
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
private val echoMessageQueue: BlockingQueue<LogMessage>? =
- if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
+ if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
init {
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
@@ -133,11 +135,11 @@ class LogBuffer @JvmOverloads constructor(
*/
@JvmOverloads
inline fun log(
- tag: String,
- level: LogLevel,
- messageInitializer: MessageInitializer,
- noinline messagePrinter: MessagePrinter,
- exception: Throwable? = null,
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
) {
val message = obtain(tag, level, messagePrinter, exception)
messageInitializer(message)
@@ -152,14 +154,13 @@ class LogBuffer @JvmOverloads constructor(
* log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
* an initializer and a message printer.
*
- * Log buffers are limited by the number of entries, so logging more frequently
- * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the
- * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
- * add more detail to every log may do more to improve overall logging than adding more logs
- * with this method.
+ * Log buffers are limited by the number of entries, so logging more frequently will limit the
+ * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a
+ * bug report more actionable, so using the [log] with a messagePrinter to add more detail to
+ * every log may do more to improve overall logging than adding more logs with this method.
*/
fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
- log(tag, level, {str1 = message}, { str1!! })
+ log(tag, level, { str1 = message }, { str1!! })
/**
* You should call [log] instead of this method.
@@ -172,10 +173,10 @@ class LogBuffer @JvmOverloads constructor(
*/
@Synchronized
fun obtain(
- tag: String,
- level: LogLevel,
- messagePrinter: MessagePrinter,
- exception: Throwable? = null,
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
): LogMessage {
if (!mutable) {
return FROZEN_MESSAGE
@@ -189,8 +190,7 @@ class LogBuffer @JvmOverloads constructor(
* You should call [log] instead of this method.
*
* After acquiring a message via [obtain], call this method to signal to the buffer that you
- * have finished filling in its data fields. The message will be echoed to logcat if
- * necessary.
+ * have finished filling in its data fields. The message will be echoed to logcat if necessary.
*/
@Synchronized
fun commit(message: LogMessage) {
@@ -213,7 +213,8 @@ class LogBuffer @JvmOverloads constructor(
/** Sends message to echo after determining whether to use Logcat and/or systrace. */
private fun echoToDesiredEndpoints(message: LogMessage) {
- val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
+ val includeInLogcat =
+ logcatEchoTracker.isBufferLoggable(name, message.level) ||
logcatEchoTracker.isTagLoggable(message.tag, message.level)
echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
}
@@ -221,7 +222,12 @@ class LogBuffer @JvmOverloads constructor(
/** Converts the entire buffer to a newline-delimited string */
@Synchronized
fun dump(pw: PrintWriter, tailLength: Int) {
- val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
+ val iterationStart =
+ if (tailLength <= 0) {
+ 0
+ } else {
+ max(0, buffer.size - tailLength)
+ }
for (i in iterationStart until buffer.size) {
buffer[i].dump(pw)
@@ -229,9 +235,9 @@ class LogBuffer @JvmOverloads constructor(
}
/**
- * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called.
- * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy
- * values if necessary.
+ * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls
+ * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if
+ * necessary.
*/
@Synchronized
fun freeze() {
@@ -241,9 +247,7 @@ class LogBuffer @JvmOverloads constructor(
}
}
- /**
- * Undoes the effects of calling [freeze].
- */
+ /** Undoes the effects of calling [freeze]. */
@Synchronized
fun unfreeze() {
if (frozen) {
@@ -265,8 +269,11 @@ class LogBuffer @JvmOverloads constructor(
}
private fun echoToSystrace(message: LogMessage, strMessage: String) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
- "$name - ${message.level.shortString} ${message.tag}: $strMessage")
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ "UI Events",
+ "$name - ${message.level.shortString} ${message.tag}: $strMessage"
+ )
}
private fun echoToLogcat(message: LogMessage, strMessage: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
index 53f231c9f9d2..b036cf0be1d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
@@ -14,17 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.util.Log
-/**
- * Enum version of @Log.Level
- */
-enum class LogLevel(
- @Log.Level val nativeLevel: Int,
- val shortString: String
-) {
+/** Enum version of @Log.Level */
+enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {
VERBOSE(Log.VERBOSE, "V"),
DEBUG(Log.DEBUG, "D"),
INFO(Log.INFO, "I"),
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
index dae2592e116c..9468681289bf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import java.io.PrintWriter
import java.text.SimpleDateFormat
@@ -29,9 +29,10 @@ import java.util.Locale
*
* When a message is logged, the code doing the logging stores data in one or more of the generic
* fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
- * readable string. Thus, for every log type there must be a specialized initializer function that
- * stores data specific to that log type and a specialized printer function that prints that data.
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a
+ * human- readable string. Thus, for every log type there must be a specialized initializer function
+ * that stores data specific to that log type and a specialized printer function that prints that
+ * data.
*
* See [LogBuffer.log] for more information.
*/
@@ -55,9 +56,7 @@ interface LogMessage {
var bool3: Boolean
var bool4: Boolean
- /**
- * Function that dumps the [LogMessage] to the provided [writer].
- */
+ /** Function that dumps the [LogMessage] to the provided [writer]. */
fun dump(writer: PrintWriter) {
val formattedTimestamp = DATE_FORMAT.format(timestamp)
val shortLevel = level.shortString
@@ -68,12 +67,12 @@ interface LogMessage {
}
/**
- * A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * A function that will be called if and when the message needs to be dumped to logcat or a bug
+ * report. It should read the data stored by the initializer and convert it to a human-readable
+ * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
+ * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing
+ * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call,
+ * thwarting our attempts at avoiding any sort of allocation.
*/
typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
index 4dd6f652d1c7..f2a6a91adcdf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Recyclable implementation of [LogMessage].
- */
+/** Recyclable implementation of [LogMessage]. */
data class LogMessageImpl(
override var level: LogLevel,
override var tag: String,
@@ -68,23 +66,24 @@ data class LogMessageImpl(
companion object Factory {
fun create(): LogMessageImpl {
return LogMessageImpl(
- LogLevel.DEBUG,
- DEFAULT_TAG,
- 0,
- DEFAULT_PRINTER,
- null,
- null,
- null,
- null,
- 0,
- 0,
- 0,
- 0,
- 0.0,
- false,
- false,
- false,
- false)
+ LogLevel.DEBUG,
+ DEFAULT_TAG,
+ 0,
+ DEFAULT_PRINTER,
+ null,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.0,
+ false,
+ false,
+ false,
+ false
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
index 8cda4236bc87..cfe894f276a0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
@@ -14,24 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Keeps track of which [LogBuffer] messages should also appear in logcat.
- */
+/** Keeps track of which [LogBuffer] messages should also appear in logcat. */
interface LogcatEchoTracker {
- /**
- * Whether [bufferName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean
- /**
- * Whether [tagName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [tagName] should echo messages of [level] or higher to logcat. */
fun isTagLoggable(tagName: String, level: LogLevel): Boolean
- /**
- * Whether to log messages in a background thread.
- */
+ /** Whether to log messages in a background thread. */
val logInBackgroundThread: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
index 40b0cdc173d8..d3fabaccb6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.content.ContentResolver
import android.database.ContentObserver
@@ -36,19 +36,15 @@ import android.provider.Settings
* $ adb shell settings put global systemui/tag/<tag> <level>
* ```
*/
-class LogcatEchoTrackerDebug private constructor(
- private val contentResolver: ContentResolver
-) : LogcatEchoTracker {
+class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) :
+ LogcatEchoTracker {
private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf()
private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf()
override val logInBackgroundThread = true
companion object Factory {
@JvmStatic
- fun create(
- contentResolver: ContentResolver,
- mainLooper: Looper
- ): LogcatEchoTrackerDebug {
+ fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug {
val tracker = LogcatEchoTrackerDebug(contentResolver)
tracker.attach(mainLooper)
return tracker
@@ -57,37 +53,35 @@ class LogcatEchoTrackerDebug private constructor(
private fun attach(mainLooper: Looper) {
contentResolver.registerContentObserver(
- Settings.Global.getUriFor(BUFFER_PATH),
- true,
- object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- cachedBufferLevels.clear()
- }
- })
+ Settings.Global.getUriFor(BUFFER_PATH),
+ true,
+ object : ContentObserver(Handler(mainLooper)) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ cachedBufferLevels.clear()
+ }
+ }
+ )
contentResolver.registerContentObserver(
- Settings.Global.getUriFor(TAG_PATH),
- true,
- object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- cachedTagLevels.clear()
- }
- })
+ Settings.Global.getUriFor(TAG_PATH),
+ true,
+ object : ContentObserver(Handler(mainLooper)) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ cachedTagLevels.clear()
+ }
+ }
+ )
}
- /**
- * Whether [bufferName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
@Synchronized
override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean {
return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal
}
- /**
- * Whether [tagName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [tagName] should echo messages of [level] or higher to logcat. */
@Synchronized
override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
index 1a4ad1907ff1..3c8bda4a44e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Production version of [LogcatEchoTracker] that isn't configurable.
- */
+/** Production version of [LogcatEchoTracker] that isn't configurable. */
class LogcatEchoTrackerProd : LogcatEchoTracker {
override val logInBackgroundThread = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
index 97dc842ec699..68d78907f028 100644
--- a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.collection
+package com.android.systemui.plugins.util
import kotlin.math.max
@@ -32,19 +32,16 @@ import kotlin.math.max
* @param factory A function that creates a fresh instance of T. Used by the buffer while it's
* growing to [maxSize].
*/
-class RingBuffer<T>(
- private val maxSize: Int,
- private val factory: () -> T
-) : Iterable<T> {
+class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> {
private val buffer = MutableList<T?>(maxSize) { null }
/**
* An abstract representation that points to the "end" of the buffer. Increments every time
- * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into
- * the backing array. Always points to the "next" available slot in the buffer. Before the
- * buffer has completely filled, the value pointed to will be null. Afterward, it will be the
- * value at the "beginning" of the buffer.
+ * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the
+ * backing array. Always points to the "next" available slot in the buffer. Before the buffer
+ * has completely filled, the value pointed to will be null. Afterward, it will be the value at
+ * the "beginning" of the buffer.
*
* This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,
* omega will overflow after a little under three million years of continuous operation.
@@ -60,24 +57,23 @@ class RingBuffer<T>(
/**
* Advances the buffer's position by one and returns the value that is now present at the "end"
- * of the buffer. If the buffer is not yet full, uses [factory] to create a new item.
- * Otherwise, reuses the value that was previously at the "beginning" of the buffer.
+ * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise,
+ * reuses the value that was previously at the "beginning" of the buffer.
*
- * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that
- * was previously stored on it.
+ * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was
+ * previously stored on it.
*/
fun advance(): T {
val index = indexOf(omega)
omega += 1
- val entry = buffer[index] ?: factory().also {
- buffer[index] = it
- }
+ val entry = buffer[index] ?: factory().also { buffer[index] = it }
return entry
}
/**
* Returns the value stored at [index], which can range from 0 (the "start", or oldest element
- * of the buffer) to [size] - 1 (the "end", or newest element of the buffer).
+ * of the buffer) to [size]
+ * - 1 (the "end", or newest element of the buffer).
*/
operator fun get(index: Int): T {
if (index < 0 || index >= size) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
index 56aff3c2fc8b..a39b856f0f49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
@@ -2,6 +2,7 @@ package com.android.systemui.log
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.log.LogBuffer
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -18,8 +19,7 @@ class LogBufferTest : SysuiTestCase() {
private lateinit var outputWriter: StringWriter
- @Mock
- private lateinit var logcatEchoTracker: LogcatEchoTracker
+ @Mock private lateinit var logcatEchoTracker: LogcatEchoTracker
@Before
fun setup() {
@@ -67,15 +67,17 @@ class LogBufferTest : SysuiTestCase() {
@Test
fun dump_writesCauseAndStacktrace() {
buffer = createBuffer()
- val exception = createTestException("Exception message",
+ val exception =
+ createTestException(
+ "Exception message",
"TestClass",
- cause = createTestException("The real cause!", "TestClass"))
+ cause = createTestException("The real cause!", "TestClass")
+ )
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedString = dumpBuffer()
- assertThat(dumpedString)
- .contains("Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("Caused by: java.lang.RuntimeException: The real cause!")
assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")
assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")
}
@@ -85,49 +87,47 @@ class LogBufferTest : SysuiTestCase() {
buffer = createBuffer()
val exception = RuntimeException("Root exception message")
exception.addSuppressed(
- createTestException(
- "First suppressed exception",
- "FirstClass",
- createTestException("Cause of suppressed exp", "ThirdClass")
- ))
- exception.addSuppressed(
- createTestException("Second suppressed exception", "SecondClass"))
+ createTestException(
+ "First suppressed exception",
+ "FirstClass",
+ createTestException("Cause of suppressed exp", "ThirdClass")
+ )
+ )
+ exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedStr = dumpBuffer()
// first suppressed exception
assertThat(dumpedStr)
- .contains("Suppressed: " +
- "java.lang.RuntimeException: First suppressed exception")
+ .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception")
assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)")
assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)")
assertThat(dumpedStr)
- .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)")
assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)")
// second suppressed exception
assertThat(dumpedStr)
- .contains("Suppressed: " +
- "java.lang.RuntimeException: Second suppressed exception")
+ .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception")
assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)")
assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)")
}
private fun createTestException(
- message: String,
- errorClass: String,
- cause: Throwable? = null,
+ message: String,
+ errorClass: String,
+ cause: Throwable? = null,
): Exception {
val exception = RuntimeException(message, cause)
- exception.stackTrace = (1..5).map { lineNumber ->
- StackTraceElement(errorClass,
- "TestMethod",
- "$errorClass.java",
- lineNumber)
- }.toTypedArray()
+ exception.stackTrace =
+ (1..5)
+ .map { lineNumber ->
+ StackTraceElement(errorClass, "TestMethod", "$errorClass.java", lineNumber)
+ }
+ .toTypedArray()
return exception
}
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/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 5dc34b9db594..a565988c14ad 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -73,8 +73,8 @@
android:singleLine="true"
android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
- android:transformPivotX="0sp"
- android:transformPivotY="20sp"
+ android:transformPivotX="0dp"
+ android:transformPivotY="24dp"
android:scaleX="1"
android:scaleY="1"
/>
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/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 01c9ac1b9d15..66f0e7543469 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -519,7 +519,7 @@
<dimen name="qs_tile_margin_horizontal">8dp</dimen>
<dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
<dimen name="qs_tile_margin_top_bottom">4dp</dimen>
- <dimen name="qs_brightness_margin_top">8dp</dimen>
+ <dimen name="qs_brightness_margin_top">12dp</dimen>
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
@@ -572,6 +572,7 @@
<dimen name="qs_header_row_min_height">48dp</dimen>
<dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+ <dimen name="new_qs_header_non_clickable_element_height">20dp</dimen>
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 3164ed1e6751..e30d4415a0c4 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -28,4 +28,11 @@
<!-- The time it takes for the over scroll release animation to complete, in milli seconds. -->
<integer name="lockscreen_shade_over_scroll_release_duration">0</integer>
+
+ <!-- Values for transition of QS Headers -->
+ <integer name="fade_out_complete_frame">14</integer>
+ <integer name="fade_in_start_frame">58</integer>
+ <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
+ fade_out_complete_frame -->
+ <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a734fa744b48..e76887babc50 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -128,11 +128,10 @@
<!-- This is hard coded to be sans-serif-condensed to match the icons -->
<style name="TextAppearance.QS.Status">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:letterSpacing">0.01</item>
- <item name="android:lineHeight">20sp</item>
</style>
<style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
@@ -143,12 +142,10 @@
<style name="TextAppearance.QS.Status.Carriers" />
<style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.QS.Status.Build">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
@@ -198,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">
@@ -218,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>
@@ -242,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>
@@ -285,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>
@@ -311,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/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index f3866c08cbfc..de855e275f5f 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -27,67 +27,60 @@
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="49"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
+ app:percentY="0.5"
app:sizePercent="1"
- app:framePosition="51"
+ app:framePosition="50"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyAttribute
app:motionTarget="@id/date"
- app:framePosition="30"
+ app:framePosition="14"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/date"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
- app:keyPositionType="pathRelative"
- app:percentX="0"
- app:percentY="0"
- app:framePosition="0"
- app:curveFit="linear"
- app:motionTarget="@id/statusIcons" />
- <KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="50"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:sizePercent="1"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyAttribute
app:motionTarget="@id/statusIcons"
- app:framePosition="30"
+ app:framePosition="@integer/fade_out_complete_frame"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/statusIcons"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="50"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
@@ -95,27 +88,27 @@
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
app:motionTarget="@id/batteryRemainingIcon" />
<KeyAttribute
app:motionTarget="@id/batteryRemainingIcon"
- app:framePosition="30"
+ app:framePosition="@integer/fade_out_complete_frame"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/batteryRemainingIcon"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
app:motionTarget="@id/carrier_group"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
@@ -126,7 +119,7 @@
android:alpha="0" />
<KeyAttribute
app:motionTarget="@id/carrier_group"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0" />
</KeyFrameSet>
</Transition>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index a82684d0358b..88b4f43b440b 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -43,7 +43,8 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
@@ -57,8 +58,8 @@
android:id="@+id/statusIcons">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
@@ -71,9 +72,9 @@
android:id="@+id/batteryRemainingIcon">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index f39e6bd65b86..d8a4e7752960 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -40,13 +40,13 @@
android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintBottom_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
/>
<Transform
- android:scaleX="2.4"
- android:scaleY="2.4"
+ android:scaleX="2.57"
+ android:scaleY="2.57"
/>
</Constraint>
@@ -54,11 +54,11 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
@@ -87,7 +87,7 @@
android:id="@+id/statusIcons">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
@@ -101,8 +101,8 @@
android:id="@+id/batteryRemainingIcon">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
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 f03fee4b0c2d..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,7 @@ 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
import com.android.systemui.plugins.ClockMetadata
@@ -30,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
@@ -42,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()
@@ -201,6 +193,7 @@ open class ClockRegistry(
val provider: ClockProvider
)
+ @Keep
private data class ClockSetting(
val clockId: ClockId,
val _applied_timestamp: Long?
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/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index dd2e55d4e7d7..cd4b9994ccca 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.shared.regionsampling
+import android.graphics.Color
import android.graphics.Rect
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -33,18 +34,19 @@ open class RegionSamplingInstance(
regionSamplingEnabled: Boolean,
updateFun: UpdateColorCallback
) {
- private var isDark = RegionDarkness.DEFAULT
+ private var regionDarkness = RegionDarkness.DEFAULT
private var samplingBounds = Rect()
private val tmpScreenLocation = IntArray(2)
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
-
+ private var lightForegroundColor = Color.WHITE
+ private var darkForegroundColor = Color.BLACK
/**
* Interface for method to be passed into RegionSamplingHelper
*/
@FunctionalInterface
interface UpdateColorCallback {
/**
- * Method to update the text colors after clock darkness changed.
+ * Method to update the foreground colors after clock darkness changed.
*/
fun updateColors()
}
@@ -59,6 +61,30 @@ open class RegionSamplingInstance(
return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
}
+ /**
+ * Sets the colors to be used for Dark and Light Foreground.
+ *
+ * @param lightColor The color used for Light Foreground.
+ * @param darkColor The color used for Dark Foreground.
+ */
+ fun setForegroundColors(lightColor: Int, darkColor: Int) {
+ lightForegroundColor = lightColor
+ darkForegroundColor = darkColor
+ }
+
+ /**
+ * Determines which foreground color to use based on region darkness.
+ *
+ * @return the determined foreground color
+ */
+ fun currentForegroundColor(): Int{
+ return if (regionDarkness.isDark) {
+ lightForegroundColor
+ } else {
+ darkForegroundColor
+ }
+ }
+
private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
return if (isRegionDark) {
RegionDarkness.DARK
@@ -68,7 +94,7 @@ open class RegionSamplingInstance(
}
fun currentRegionDarkness(): RegionDarkness {
- return isDark
+ return regionDarkness
}
/**
@@ -97,7 +123,7 @@ open class RegionSamplingInstance(
regionSampler = createRegionSamplingHelper(sampledView,
object : SamplingCallback {
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
- isDark = convertToClockDarkness(isRegionDark)
+ regionDarkness = convertToClockDarkness(isRegionDark)
updateFun.updateColors()
}
/**
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/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
deleted file mode 100644
index 30c062b66da9..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * 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.systemui.shared.system;
-
-import android.graphics.HardwareRenderer;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.Message;
-import android.os.Trace;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.View;
-import android.view.ViewRootImpl;
-
-import java.util.function.Consumer;
-
-/**
- * Helper class to apply surface transactions in sync with RenderThread.
- *
- * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't
- * currently reference that class from the shared lib as it is hidden.
- */
-public class SyncRtSurfaceTransactionApplierCompat {
-
- public static final int FLAG_ALL = 0xffffffff;
- public static final int FLAG_ALPHA = 1;
- public static final int FLAG_MATRIX = 1 << 1;
- public static final int FLAG_WINDOW_CROP = 1 << 2;
- public static final int FLAG_LAYER = 1 << 3;
- public static final int FLAG_CORNER_RADIUS = 1 << 4;
- public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
- public static final int FLAG_VISIBILITY = 1 << 6;
- public static final int FLAG_RELATIVE_LAYER = 1 << 7;
- public static final int FLAG_SHADOW_RADIUS = 1 << 8;
-
- private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
-
- private final SurfaceControl mBarrierSurfaceControl;
- private final ViewRootImpl mTargetViewRootImpl;
- private final Handler mApplyHandler;
-
- private int mSequenceNumber = 0;
- private int mPendingSequenceNumber = 0;
- private Runnable mAfterApplyCallback;
-
- /**
- * @param targetView The view in the surface that acts as synchronization anchor.
- */
- public SyncRtSurfaceTransactionApplierCompat(View targetView) {
- mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
- mBarrierSurfaceControl = mTargetViewRootImpl != null
- ? mTargetViewRootImpl.getSurfaceControl() : null;
-
- mApplyHandler = new Handler(new Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
- onApplyMessage(msg.arg1);
- return true;
- }
- return false;
- }
- });
- }
-
- private void onApplyMessage(int seqNo) {
- mSequenceNumber = seqNo;
- if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) {
- Runnable r = mAfterApplyCallback;
- mAfterApplyCallback = null;
- r.run();
- }
- }
-
- /**
- * Schedules applying surface parameters on the next frame.
- *
- * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
- * this method to avoid synchronization issues.
- */
- public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
- if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) {
- return;
- }
-
- mPendingSequenceNumber++;
- final int toApplySeqNo = mPendingSequenceNumber;
- mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
- @Override
- public void onFrameDraw(long frame) {
- if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
- Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
- .sendToTarget();
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame);
- Transaction t = new Transaction();
- for (int i = params.length - 1; i >= 0; i--) {
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
- params[i];
- surfaceParams.applyTo(t);
- }
- if (mTargetViewRootImpl != null) {
- mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
- } else {
- t.apply();
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
- .sendToTarget();
- }
- });
-
- // Make sure a frame gets scheduled.
- mTargetViewRootImpl.getView().invalidate();
- }
-
- /**
- * Calls the runnable when any pending apply calls have completed
- */
- public void addAfterApplyCallback(final Runnable afterApplyCallback) {
- if (mSequenceNumber == mPendingSequenceNumber) {
- afterApplyCallback.run();
- } else {
- if (mAfterApplyCallback == null) {
- mAfterApplyCallback = afterApplyCallback;
- } else {
- final Runnable oldCallback = mAfterApplyCallback;
- mAfterApplyCallback = new Runnable() {
- @Override
- public void run() {
- afterApplyCallback.run();
- oldCallback.run();
- }
- };
- }
- }
- }
-
- public static void applyParams(TransactionCompat t,
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
- params.applyTo(t.mTransaction);
- }
-
- /**
- * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
- * attached if necessary.
- */
- public static void create(final View targetView,
- final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
- if (targetView == null) {
- // No target view, no applier
- callback.accept(null);
- } else if (targetView.getViewRootImpl() != null) {
- // Already attached, we're good to go
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
- } else {
- // Haven't been attached before we can get the view root
- targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- targetView.removeOnAttachStateChangeListener(this);
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- // Do nothing
- }
- });
- }
- }
-
- public static class SurfaceParams {
- public static class Builder {
- final SurfaceControl surface;
- int flags;
- float alpha;
- float cornerRadius;
- int backgroundBlurRadius;
- Matrix matrix;
- Rect windowCrop;
- int layer;
- SurfaceControl relativeTo;
- int relativeLayer;
- boolean visible;
- float shadowRadius;
-
- /**
- * @param surface The surface to modify.
- */
- public Builder(SurfaceControl surface) {
- this.surface = surface;
- }
-
- /**
- * @param alpha The alpha value to apply to the surface.
- * @return this Builder
- */
- public Builder withAlpha(float alpha) {
- this.alpha = alpha;
- flags |= FLAG_ALPHA;
- return this;
- }
-
- /**
- * @param matrix The matrix to apply to the surface.
- * @return this Builder
- */
- public Builder withMatrix(Matrix matrix) {
- this.matrix = new Matrix(matrix);
- flags |= FLAG_MATRIX;
- return this;
- }
-
- /**
- * @param windowCrop The window crop to apply to the surface.
- * @return this Builder
- */
- public Builder withWindowCrop(Rect windowCrop) {
- this.windowCrop = new Rect(windowCrop);
- flags |= FLAG_WINDOW_CROP;
- return this;
- }
-
- /**
- * @param layer The layer to assign the surface.
- * @return this Builder
- */
- public Builder withLayer(int layer) {
- this.layer = layer;
- flags |= FLAG_LAYER;
- return this;
- }
-
- /**
- * @param relativeTo The surface that's set relative layer to.
- * @param relativeLayer The relative layer.
- * @return this Builder
- */
- public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) {
- this.relativeTo = relativeTo;
- this.relativeLayer = relativeLayer;
- flags |= FLAG_RELATIVE_LAYER;
- return this;
- }
-
- /**
- * @param radius the Radius for rounded corners to apply to the surface.
- * @return this Builder
- */
- public Builder withCornerRadius(float radius) {
- this.cornerRadius = radius;
- flags |= FLAG_CORNER_RADIUS;
- return this;
- }
-
- /**
- * @param radius the Radius for the shadows to apply to the surface.
- * @return this Builder
- */
- public Builder withShadowRadius(float radius) {
- this.shadowRadius = radius;
- flags |= FLAG_SHADOW_RADIUS;
- return this;
- }
-
- /**
- * @param radius the Radius for blur to apply to the background surfaces.
- * @return this Builder
- */
- public Builder withBackgroundBlur(int radius) {
- this.backgroundBlurRadius = radius;
- flags |= FLAG_BACKGROUND_BLUR_RADIUS;
- return this;
- }
-
- /**
- * @param visible The visibility to apply to the surface.
- * @return this Builder
- */
- public Builder withVisibility(boolean visible) {
- this.visible = visible;
- flags |= FLAG_VISIBILITY;
- return this;
- }
-
- /**
- * @return a new SurfaceParams instance
- */
- public SurfaceParams build() {
- return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
- relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible,
- shadowRadius);
- }
- }
-
- private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
- Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
- float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
- this.flags = flags;
- this.surface = surface;
- this.alpha = alpha;
- this.matrix = matrix;
- this.windowCrop = windowCrop;
- this.layer = layer;
- this.relativeTo = relativeTo;
- this.relativeLayer = relativeLayer;
- this.cornerRadius = cornerRadius;
- this.backgroundBlurRadius = backgroundBlurRadius;
- this.visible = visible;
- this.shadowRadius = shadowRadius;
- }
-
- private final int flags;
- private final float[] mTmpValues = new float[9];
-
- public final SurfaceControl surface;
- public final float alpha;
- public final float cornerRadius;
- public final int backgroundBlurRadius;
- public final Matrix matrix;
- public final Rect windowCrop;
- public final int layer;
- public final SurfaceControl relativeTo;
- public final int relativeLayer;
- public final boolean visible;
- public final float shadowRadius;
-
- public void applyTo(SurfaceControl.Transaction t) {
- if ((flags & FLAG_MATRIX) != 0) {
- t.setMatrix(surface, matrix, mTmpValues);
- }
- if ((flags & FLAG_WINDOW_CROP) != 0) {
- t.setWindowCrop(surface, windowCrop);
- }
- if ((flags & FLAG_ALPHA) != 0) {
- t.setAlpha(surface, alpha);
- }
- if ((flags & FLAG_LAYER) != 0) {
- t.setLayer(surface, layer);
- }
- if ((flags & FLAG_CORNER_RADIUS) != 0) {
- t.setCornerRadius(surface, cornerRadius);
- }
- if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) {
- t.setBackgroundBlurRadius(surface, backgroundBlurRadius);
- }
- if ((flags & FLAG_VISIBILITY) != 0) {
- if (visible) {
- t.show(surface);
- } else {
- t.hide(surface);
- }
- }
- if ((flags & FLAG_RELATIVE_LAYER) != 0) {
- t.setRelativeLayer(surface, relativeTo, relativeLayer);
- }
- if ((flags & FLAG_SHADOW_RADIUS) != 0) {
- t.setShadowRadius(surface, shadowRadius);
- }
- }
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
deleted file mode 100644
index 43a882a5f6be..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.systemui.shared.system;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-
-public class TransactionCompat {
-
- final Transaction mTransaction;
-
- final float[] mTmpValues = new float[9];
-
- public TransactionCompat() {
- mTransaction = new Transaction();
- }
-
- public void apply() {
- mTransaction.apply();
- }
-
- public TransactionCompat show(SurfaceControl surfaceControl) {
- mTransaction.show(surfaceControl);
- return this;
- }
-
- public TransactionCompat hide(SurfaceControl surfaceControl) {
- mTransaction.hide(surfaceControl);
- return this;
- }
-
- public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
- mTransaction.setPosition(surfaceControl, x, y);
- return this;
- }
-
- public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
- mTransaction.setBufferSize(surfaceControl, w, h);
- return this;
- }
-
- public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
- mTransaction.setLayer(surfaceControl, z);
- return this;
- }
-
- public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
- mTransaction.setAlpha(surfaceControl, alpha);
- return this;
- }
-
- public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
- mTransaction.setOpaque(surfaceControl, opaque);
- return this;
- }
-
- public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
- float dtdy, float dsdy) {
- mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
- return this;
- }
-
- public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
- mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
- return this;
- }
-
- public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
- mTransaction.setWindowCrop(surfaceControl, crop);
- return this;
- }
-
- public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
- mTransaction.setCornerRadius(surfaceControl, radius);
- return this;
- }
-
- public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
- mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
- return this;
- }
-
- public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
- mTransaction.setColor(surfaceControl, color);
- return this;
- }
-
- public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl,
- SurfaceControl relativeTo, int z) {
- t.setRelativeLayer(surfaceControl, relativeTo, z);
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 0075ddd73cd3..5277e40492e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -16,19 +16,29 @@
package com.android.keyguard
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Color
import android.util.AttributeSet
+import android.view.View
import com.android.settingslib.Utils
+import com.android.systemui.animation.Interpolators
/** Displays security messages for the keyguard bouncer. */
-class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
+open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
KeyguardMessageArea(context, attrs) {
private val DEFAULT_COLOR = -1
private var mDefaultColorState: ColorStateList? = null
private var mNextMessageColorState: ColorStateList? = ColorStateList.valueOf(DEFAULT_COLOR)
+ private val animatorSet = AnimatorSet()
+ private var textAboutToShow: CharSequence? = null
+ protected open val SHOW_DURATION_MILLIS = 150L
+ protected open val HIDE_DURATION_MILLIS = 200L
override fun updateTextColor() {
var colorState = mDefaultColorState
@@ -58,4 +68,46 @@ class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
mDefaultColorState = Utils.getColorAttr(context, android.R.attr.textColorPrimary)
super.reloadColor()
}
+
+ override fun setMessage(msg: CharSequence?) {
+ if (msg == textAboutToShow || msg == text) {
+ return
+ }
+ textAboutToShow = msg
+
+ if (animatorSet.isRunning) {
+ animatorSet.cancel()
+ textAboutToShow = null
+ }
+
+ val hideAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f).apply {
+ duration = HIDE_DURATION_MILLIS
+ interpolator = Interpolators.STANDARD_ACCELERATE
+ }
+
+ hideAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ super@BouncerKeyguardMessageArea.setMessage(msg)
+ }
+ }
+ )
+ val showAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f).apply {
+ duration = SHOW_DURATION_MILLIS
+ interpolator = Interpolators.STANDARD_DECELERATE
+ }
+
+ showAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ textAboutToShow = null
+ }
+ }
+ )
+
+ animatorSet.playSequentially(hideAnimator, showAnimator)
+ animatorSet.start()
+ }
}
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/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index f26b9057dc7c..73229c321079 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -152,6 +152,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
public void startAppearAnimation() {
+ mMessageAreaController.setMessage(getInitialMessageResId());
mView.startAppearAnimation();
}
@@ -169,6 +170,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return view.indexOfChild(mView);
}
+ /** Determines the message to show in the bouncer when it first appears. */
+ protected int getInitialMessageResId() {
+ return 0;
+ }
+
/** Factory for a {@link KeyguardInputViewController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index c2802f7b6843..2bd3ca59b740 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,7 +18,6 @@ package com.android.keyguard;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
-import android.text.TextUtils;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -100,15 +99,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mView.setMessage(resId);
}
- /**
- * Set Text if KeyguardMessageArea is empty.
- */
- public void setMessageIfEmpty(int resId) {
- if (TextUtils.isEmpty(mView.getText())) {
- setMessage(resId);
- }
- }
-
public void setNextMessageColor(ColorStateList colorState) {
mView.setNextMessageColor(colorState);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 29e912fdab32..0025986c0e5c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -187,7 +187,7 @@ public class KeyguardPasswordViewController
@Override
void resetState() {
mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
- mMessageAreaController.setMessage("");
+ mMessageAreaController.setMessage(getInitialMessageResId());
final boolean wasDisabled = mPasswordEntry.isEnabled();
mView.setPasswordEntryEnabled(true);
mView.setPasswordEntryInputEnabled(true);
@@ -207,7 +207,6 @@ public class KeyguardPasswordViewController
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password);
}
private void showInput() {
@@ -324,4 +323,9 @@ public class KeyguardPasswordViewController
//enabled input method subtype (The current IME should be LatinIME.)
|| imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_password;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 987164557a7a..1f0bd54f8e09 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -298,12 +298,6 @@ public class KeyguardPatternViewController
}
@Override
- public void onResume(int reason) {
- super.onResume(reason);
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern);
- }
-
- @Override
public boolean needsInput() {
return false;
}
@@ -361,7 +355,7 @@ public class KeyguardPatternViewController
}
private void displayDefaultSecurityMessage() {
- mMessageAreaController.setMessage("");
+ mMessageAreaController.setMessage(getInitialMessageResId());
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
@@ -392,4 +386,9 @@ public class KeyguardPatternViewController
}.start();
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pattern;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 59a018ad51df..f7423ed12e68 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,7 +127,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
public void onResume(int reason) {
super.onResume(reason);
mPasswordEntry.requestFocus();
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 89fcc47caf57..7876f071fdf5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -76,20 +76,13 @@ public class KeyguardPinViewController
}
@Override
- void resetState() {
- super.resetState();
- mMessageAreaController.setMessage("");
- }
-
- @Override
- public void startAppearAnimation() {
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- super.startAppearAnimation();
- }
-
- @Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f5582761c0ae..8699e324adf9 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;
@@ -3796,4 +3795,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/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2c2ab7b39161..6264ce7273f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,9 +17,9 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 50012a589b5a..46f3d4e5f6aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,15 +16,15 @@
package com.android.keyguard.logging
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
import com.android.systemui.log.dagger.KeyguardLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.MessageInitializer
+import com.android.systemui.plugins.log.MessagePrinter
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2eee95738b7b..82b32cf616ec 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -22,13 +22,13 @@ import android.telephony.SubscriptionInfo
import com.android.keyguard.ActiveUnlockConfig
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 242a5983a59d..9493975ca00f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -792,7 +792,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(mScaleFactor);
mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
- new UdfpsOverlayParams(mUdfpsBounds, mCachedDisplayInfo.getNaturalWidth(),
+ new UdfpsOverlayParams(mUdfpsBounds, new Rect(
+ 0, mCachedDisplayInfo.getNaturalHeight() / 2,
+ mCachedDisplayInfo.getNaturalWidth(),
+ mCachedDisplayInfo.getNaturalHeight()),
+ mCachedDisplayInfo.getNaturalWidth(),
mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
mCachedDisplayInfo.rotation));
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
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/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 39199d194cc9..0d08b4307f12 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -16,12 +16,12 @@
package com.android.systemui.biometrics
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.UdfpsLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
new file mode 100644
index 000000000000..6e78f3d3d6aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.biometrics
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.os.Handler
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.Execution
+import java.util.*
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "UdfpsOverlay"
+
+@SuppressLint("ClickableViewAccessibility")
+@SysUISingleton
+class UdfpsOverlay
+@Inject
+constructor(
+ private val context: Context,
+ private val execution: Execution,
+ private val windowManager: WindowManager,
+ private val fingerprintManager: FingerprintManager?,
+ private val handler: Handler,
+ private val biometricExecutor: Executor,
+ private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
+ private val fgExecutor: DelayableExecutor,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val authController: AuthController,
+ private val udfpsLogger: UdfpsLogger
+) : CoreStartable {
+
+ /** The view, when [isShowing], or null. */
+ var overlayView: UdfpsOverlayView? = null
+ private set
+
+ private var requestId: Long = 0
+ private var onFingerDown = false
+ val size = windowManager.maximumWindowMetrics.bounds
+ val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
+
+ private var params: UdfpsOverlayParams = UdfpsOverlayParams()
+
+ private val coreLayoutParams =
+ WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+ 0 /* flags set in computeLayoutParams() */,
+ PixelFormat.TRANSLUCENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0
+ 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
+ privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ // Avoid announcing window title.
+ accessibilityTitle = " "
+ inputFeatures = INPUT_FEATURE_SPY
+ }
+
+ fun onTouch(v: View, event: MotionEvent): Boolean {
+ val view = v as UdfpsOverlayView
+
+ return when (event.action) {
+ MotionEvent.ACTION_DOWN,
+ MotionEvent.ACTION_MOVE -> {
+ onFingerDown = true
+ if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
+ biometricExecutor.execute {
+ alternateTouchProvider
+ .get()
+ .onPointerDown(
+ requestId,
+ event.x.toInt(),
+ event.y.toInt(),
+ event.touchMinor,
+ event.touchMajor
+ )
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+ }
+ }
+
+ view.configureDisplay {
+ biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
+ }
+ }
+
+ true
+ }
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_CANCEL -> {
+ if (onFingerDown && alternateTouchProvider.isPresent) {
+ biometricExecutor.execute {
+ alternateTouchProvider.get().onPointerUp(requestId)
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
+ }
+ }
+ }
+ onFingerDown = false
+ if (view.isDisplayConfigured) {
+ view.unconfigureDisplay()
+ }
+
+ true
+ }
+ else -> false
+ }
+ }
+
+ fun show(requestId: Long): Boolean {
+ this.requestId = requestId
+ if (overlayView == null && alternateTouchProvider.isPresent) {
+ UdfpsOverlayView(context, null).let {
+ it.overlayParams = params
+ it.setUdfpsDisplayMode(
+ UdfpsDisplayMode(context, execution, authController, udfpsLogger)
+ )
+ it.setOnTouchListener { v, event -> onTouch(v, event) }
+ overlayView = it
+ }
+ windowManager.addView(overlayView, coreLayoutParams)
+ return true
+ }
+
+ return false
+ }
+
+ fun hide() {
+ overlayView?.apply {
+ windowManager.removeView(this)
+ setOnTouchListener(null)
+ }
+
+ overlayView = null
+ }
+
+ @Override
+ override fun start() {
+ fingerprintManager?.addAuthenticatorsRegisteredCallback(
+ object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ override fun onAllAuthenticatorsRegistered(
+ sensors: List<FingerprintSensorPropertiesInternal>
+ ) {
+ handler.post { handleAllFingerprintAuthenticatorsRegistered(sensors) }
+ }
+ }
+ )
+ }
+
+ private fun handleAllFingerprintAuthenticatorsRegistered(
+ sensors: List<FingerprintSensorPropertiesInternal>
+ ) {
+ for (props in sensors) {
+ if (props.isAnyUdfpsType) {
+ udfpsProps.add(props)
+ }
+ }
+
+ // Setup param size
+ if (udfpsProps.isNotEmpty()) {
+ params =
+ UdfpsOverlayParams(
+ sensorBounds = udfpsProps[0].location.rect,
+ overlayBounds = Rect(0, size.height() / 2, size.width(), size.height()),
+ naturalDisplayWidth = size.width(),
+ naturalDisplayHeight = size.height(),
+ scaleFactor = 1f
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
index d725dfbfe216..c23b0f09f099 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
@@ -20,6 +20,7 @@ import android.view.Surface.Rotation
data class UdfpsOverlayParams(
val sensorBounds: Rect = Rect(),
+ val overlayBounds: Rect = Rect(),
val naturalDisplayWidth: Int = 0,
val naturalDisplayHeight: Int = 0,
val scaleFactor: Float = 1f,
@@ -40,4 +41,4 @@ data class UdfpsOverlayParams(
} else {
naturalDisplayHeight
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
new file mode 100644
index 000000000000..d37133239531
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+private const val TAG = "UdfpsOverlayView"
+
+class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
+
+ private val sensorRect = RectF()
+ var overlayParams = UdfpsOverlayParams()
+ private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
+
+ var overlayPaint = Paint()
+ var sensorPaint = Paint()
+ val centerPaint = Paint()
+
+ /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
+ var isDisplayConfigured: Boolean = false
+ private set
+
+ init {
+ this.setWillNotDraw(false)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ overlayPaint.color = Color.argb(120, 255, 0, 0)
+ overlayPaint.style = Paint.Style.FILL
+
+ sensorPaint.color = Color.argb(150, 134, 204, 255)
+ sensorPaint.style = Paint.Style.FILL
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
+ canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+ canvas.drawCircle(
+ overlayParams.sensorBounds.exactCenterX(),
+ overlayParams.sensorBounds.exactCenterY(),
+ overlayParams.sensorBounds.width().toFloat() / 2,
+ centerPaint
+ )
+ }
+
+ fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
+ mUdfpsDisplayMode = udfpsDisplayMode
+ }
+
+ fun configureDisplay(onDisplayConfigured: Runnable) {
+ isDisplayConfigured = true
+ mUdfpsDisplayMode?.enable(onDisplayConfigured)
+ }
+
+ fun unconfigureDisplay() {
+ isDisplayConfigured = false
+ mUdfpsDisplayMode?.disable(null /* onDisabled */)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index b1d6e003319a..75640b787a62 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
+import android.content.Context
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -23,9 +24,9 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
-import android.hardware.fingerprint.IUdfpsOverlayController
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.util.Log
+import android.view.LayoutInflater
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -41,14 +42,17 @@ private const val SENSOR_ID = 0
*/
@SysUISingleton
class UdfpsShell @Inject constructor(
- commandRegistry: CommandRegistry
+ commandRegistry: CommandRegistry,
+ private val udfpsOverlay: UdfpsOverlay
) : Command {
/**
* Set in [UdfpsController.java] constructor, used to show and hide the UDFPS overlay.
* TODO: inject after b/229290039 is resolved
*/
- var udfpsOverlayController: IUdfpsOverlayController? = null
+ var udfpsOverlayController: UdfpsController.UdfpsOverlayController? = null
+ var context: Context? = null
+ var inflater: LayoutInflater? = null
init {
commandRegistry.registerCommand("udfps") { this }
@@ -57,6 +61,11 @@ class UdfpsShell @Inject constructor(
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.size == 1 && args[0] == "hide") {
hideOverlay()
+ } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
+ hideOverlay()
+ showUdfpsOverlay()
+ } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
+ hideUdfpsOverlay()
} else if (args.size == 2 && args[0] == "show") {
showOverlay(getEnrollmentReason(args[1]))
} else {
@@ -104,7 +113,17 @@ class UdfpsShell @Inject constructor(
)
}
+ private fun showUdfpsOverlay() {
+ Log.v(TAG, "showUdfpsOverlay")
+ udfpsOverlay.show(REQUEST_ID)
+ }
+
+ private fun hideUdfpsOverlay() {
+ Log.v(TAG, "hideUdfpsOverlay")
+ udfpsOverlay.hide()
+ }
+
private fun hideOverlay() {
udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42bfda22..d99625a9fbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.bluetooth
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.BluetoothLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Helper class for logging bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 5b3a982ab5e2..d27708fc04d7 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -20,11 +20,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogMessage
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import javax.inject.Inject
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/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 77b65233c112..d3b5d0edd222 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -21,6 +21,8 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
+import android.os.RemoteException
+import android.service.dreams.IDreamManager
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
@@ -40,11 +42,13 @@ import javax.inject.Inject
*/
class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
- private val broadcastDispatcher: BroadcastDispatcher
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val dreamManager: IDreamManager,
) : ComponentActivity() {
private lateinit var parent: ViewGroup
private lateinit var broadcastReceiver: BroadcastReceiver
+ private var mExitToDream: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -81,17 +85,36 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
- uiController.show(parent, { finish() }, this)
+ uiController.show(parent, { finishOrReturnToDream() }, this)
ControlsAnimations.enterAnimation(parent).start()
}
- override fun onBackPressed() {
+ override fun onResume() {
+ super.onResume()
+ mExitToDream = intent.getBooleanExtra(ControlsUiController.EXIT_TO_DREAM, false)
+ }
+
+ fun finishOrReturnToDream() {
+ if (mExitToDream) {
+ try {
+ mExitToDream = false
+ dreamManager.dream()
+ return
+ } catch (e: RemoteException) {
+ // Fall through
+ }
+ }
finish()
}
+ override fun onBackPressed() {
+ finishOrReturnToDream()
+ }
+
override fun onStop() {
super.onStop()
+ mExitToDream = false
uiController.hide()
}
@@ -106,7 +129,8 @@ class ControlsActivity @Inject constructor(
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.getAction()
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ if (action == Intent.ACTION_SCREEN_OFF ||
+ action == Intent.ACTION_DREAMING_STARTED) {
finish()
}
}
@@ -114,6 +138,7 @@ class ControlsActivity @Inject constructor(
val filter = IntentFilter()
filter.addAction(Intent.ACTION_SCREEN_OFF)
+ filter.addAction(Intent.ACTION_DREAMING_STARTED)
broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 822f8f2e6191..c1cfbcb0c211 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -27,6 +27,7 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
public const val EXTRA_ANIMATE = "extra_animate"
+ public const val EXIT_TO_DREAM = "extra_exit_to_dream"
}
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 721c0ba4f865..09743ef7aebf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -218,6 +219,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
+ /** Inject into UdfpsOverlay. */
+ @Binds
+ @IntoMap
+ @ClassKey(UdfpsOverlay::class)
+ abstract fun bindUdfpsOverlay(sysui: UdfpsOverlay): CoreStartable
+
/** Inject into MediaTttSenderCoordinator. */
@Binds
@IntoMap
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/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index cc5766210406..0e1bfba8aadb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -19,10 +19,10 @@ package com.android.systemui.doze
import android.view.Display
import com.android.systemui.doze.DozeLog.Reason
import com.android.systemui.doze.DozeLog.reasonToString
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+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 java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ae412152b10e..96c35d43052e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -20,7 +20,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import android.annotation.MainThread;
-import android.app.UiModeManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Trace;
@@ -145,10 +144,9 @@ public class DozeMachine {
private final Service mDozeService;
private final WakeLock mWakeLock;
- private final AmbientDisplayConfiguration mConfig;
+ private final AmbientDisplayConfiguration mAmbientDisplayConfig;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeHost mDozeHost;
- private final UiModeManager mUiModeManager;
private final DockManager mDockManager;
private final Part[] mParts;
@@ -156,18 +154,18 @@ public class DozeMachine {
private State mState = State.UNINITIALIZED;
private int mPulseReason;
private boolean mWakeLockHeldForCurrentState = false;
+ private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL;
@Inject
- public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config,
+ public DozeMachine(@WrappedService Service service,
+ AmbientDisplayConfiguration ambientDisplayConfig,
WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
- UiModeManager uiModeManager,
DozeLog dozeLog, DockManager dockManager,
DozeHost dozeHost, Part[] parts) {
mDozeService = service;
- mConfig = config;
+ mAmbientDisplayConfig = ambientDisplayConfig;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakeLock = wakeLock;
- mUiModeManager = uiModeManager;
mDozeLog = dozeLog;
mDockManager = dockManager;
mDozeHost = dozeHost;
@@ -187,6 +185,18 @@ public class DozeMachine {
}
/**
+ * Notifies the {@link DozeMachine} that {@link Configuration} has changed.
+ */
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ if (mUiModeType == newUiModeType) return;
+ mUiModeType = newUiModeType;
+ for (Part part : mParts) {
+ part.onUiModeTypeChanged(mUiModeType);
+ }
+ }
+
+ /**
* Requests transitioning to {@code requestedState}.
*
* This can be called during a state transition, in which case it will be queued until all
@@ -211,6 +221,14 @@ public class DozeMachine {
requestState(State.DOZE_REQUEST_PULSE, pulseReason);
}
+ /**
+ * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED}
+ * or {@link State#FINISH}
+ */
+ public boolean isUninitializedOrFinished() {
+ return mState == State.UNINITIALIZED || mState == State.FINISH;
+ }
+
void onScreenState(int state) {
mDozeLog.traceDisplayState(state);
for (Part part : mParts) {
@@ -360,7 +378,7 @@ public class DozeMachine {
if (mState == State.FINISH) {
return State.FINISH;
}
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+ if (mUiModeType == Configuration.UI_MODE_TYPE_CAR
&& (requestedState.canPulse() || requestedState.staysAwake())) {
Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active");
mDozeLog.traceCarModeStarted();
@@ -411,7 +429,7 @@ public class DozeMachine {
nextState = State.FINISH;
} else if (mDockManager.isDocked()) {
nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
- } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
nextState = State.DOZE_AOD;
} else {
nextState = State.DOZE;
@@ -427,6 +445,7 @@ public class DozeMachine {
/** Dumps the current state */
public void dump(PrintWriter pw) {
pw.print(" state="); pw.println(mState);
+ pw.print(" mUiModeType="); pw.println(mUiModeType);
pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
pw.print(" wakeLock="); pw.println(mWakeLock);
pw.println("Parts:");
@@ -459,6 +478,19 @@ public class DozeMachine {
/** Sets the {@link DozeMachine} when this Part is associated with one. */
default void setDozeMachine(DozeMachine dozeMachine) {}
+
+ /**
+ * Notifies the Part about a change in {@link Configuration#uiMode}.
+ *
+ * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL},
+ * {@link Configuration#UI_MODE_TYPE_DESK},
+ * {@link Configuration#UI_MODE_TYPE_CAR},
+ * {@link Configuration#UI_MODE_TYPE_TELEVISION},
+ * {@link Configuration#UI_MODE_TYPE_APPLIANCE},
+ * {@link Configuration#UI_MODE_TYPE_WATCH},
+ * or {@link Configuration#UI_MODE_TYPE_VR_HEADSET}
+ */
+ default void onUiModeTypeChanged(int newUiModeType) {}
}
/** A wrapper interface for {@link android.service.dreams.DreamService} */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index a2eb4e3bb640..e8d7e4642e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -17,6 +17,7 @@
package com.android.systemui.doze;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.PowerManager;
import android.os.SystemClock;
import android.service.dreams.DreamService;
@@ -59,6 +60,7 @@ public class DozeService extends DreamService
mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
mDozeMachine = dozeComponent.getDozeMachine();
+ mDozeMachine.onConfigurationChanged(getResources().getConfiguration());
}
@Override
@@ -127,6 +129,12 @@ public class DozeService extends DreamService
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDozeMachine.onConfigurationChanged(newConfig);
+ }
+
+ @Override
public void onRequestHideDoze() {
if (mDozeMachine != null) {
mDozeMachine.requestState(DozeMachine.State.DOZE);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index 7ed4b35e1ee7..e6d98655b119 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -16,21 +16,13 @@
package com.android.systemui.doze;
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
-
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.os.UserHandle;
import android.text.TextUtils;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
@@ -43,7 +35,9 @@ import dagger.Lazy;
/**
* Handles suppressing doze on:
* 1. INITIALIZED, don't allow dozing at all when:
- * - in CAR_MODE
+ * - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers
+ * to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only
+ * temporary and stops when the device exits CAR_MODE
* - device is NOT provisioned
* - there's a pending authentication
* 2. PowerSaveMode active
@@ -57,35 +51,47 @@ import dagger.Lazy;
*/
@DozeScope
public class DozeSuppressor implements DozeMachine.Part {
- private static final String TAG = "DozeSuppressor";
private DozeMachine mMachine;
private final DozeHost mDozeHost;
private final AmbientDisplayConfiguration mConfig;
private final DozeLog mDozeLog;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final UiModeManager mUiModeManager;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private boolean mBroadcastReceiverRegistered;
+ private boolean mIsCarModeEnabled = false;
@Inject
public DozeSuppressor(
DozeHost dozeHost,
AmbientDisplayConfiguration config,
DozeLog dozeLog,
- BroadcastDispatcher broadcastDispatcher,
- UiModeManager uiModeManager,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
mDozeHost = dozeHost;
mConfig = config;
mDozeLog = dozeLog;
- mBroadcastDispatcher = broadcastDispatcher;
- mUiModeManager = uiModeManager;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
}
@Override
+ public void onUiModeTypeChanged(int newUiModeType) {
+ boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR;
+ if (mIsCarModeEnabled == isCarModeEnabled) {
+ return;
+ }
+ mIsCarModeEnabled = isCarModeEnabled;
+ // Do not handle the event if doze machine is not initialized yet.
+ // It will be handled upon initialization.
+ if (mMachine.isUninitializedOrFinished()) {
+ return;
+ }
+ if (mIsCarModeEnabled) {
+ handleCarModeStarted();
+ } else {
+ handleCarModeExited();
+ }
+ }
+
+ @Override
public void setDozeMachine(DozeMachine dozeMachine) {
mMachine = dozeMachine;
}
@@ -94,7 +100,6 @@ public class DozeSuppressor implements DozeMachine.Part {
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case INITIALIZED:
- registerBroadcastReceiver();
mDozeHost.addCallback(mHostCallback);
checkShouldImmediatelyEndDoze();
checkShouldImmediatelySuspendDoze();
@@ -108,14 +113,12 @@ public class DozeSuppressor implements DozeMachine.Part {
@Override
public void destroy() {
- unregisterBroadcastReceiver();
mDozeHost.removeCallback(mHostCallback);
}
private void checkShouldImmediatelySuspendDoze() {
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
- mDozeLog.traceCarModeStarted();
- mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+ if (mIsCarModeEnabled) {
+ handleCarModeStarted();
}
}
@@ -135,7 +138,7 @@ public class DozeSuppressor implements DozeMachine.Part {
@Override
public void dump(PrintWriter pw) {
- pw.println(" uiMode=" + mUiModeManager.getCurrentModeType());
+ pw.println(" isCarModeEnabled=" + mIsCarModeEnabled);
pw.println(" hasPendingAuth="
+ mBiometricUnlockControllerLazy.get().hasPendingAuthentication());
pw.println(" isProvisioned=" + mDozeHost.isProvisioned());
@@ -143,40 +146,18 @@ public class DozeSuppressor implements DozeMachine.Part {
pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());
}
- private void registerBroadcastReceiver() {
- if (mBroadcastReceiverRegistered) {
- return;
- }
- IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE);
- filter.addAction(ACTION_EXIT_CAR_MODE);
- mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- mBroadcastReceiverRegistered = true;
+ private void handleCarModeExited() {
+ mDozeLog.traceCarModeEnded();
+ mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+ ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
}
- private void unregisterBroadcastReceiver() {
- if (!mBroadcastReceiverRegistered) {
- return;
- }
- mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiverRegistered = false;
+ private void handleCarModeStarted() {
+ mDozeLog.traceCarModeStarted();
+ mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
}
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (ACTION_ENTER_CAR_MODE.equals(action)) {
- mDozeLog.traceCarModeStarted();
- mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
- } else if (ACTION_EXIT_CAR_MODE.equals(action)) {
- mDozeLog.traceCarModeEnded();
- mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
- ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
- }
- }
- };
-
- private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
+ private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
public void onPowerSaveChanged(boolean active) {
// handles suppression changes, while DozeMachine#transitionPolicy handles gating
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 0ccb222c8acc..cedd850ac2ef 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -210,7 +210,8 @@ public class DreamHomeControlsComplication implements Complication {
final Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+ .putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ .putExtra(ControlsUiController.EXIT_TO_DREAM, true);
final ActivityLaunchAnimator.Controller controller =
v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 08ef8f3d025f..478f86169718 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,7 +24,7 @@ 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.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import java.io.PrintWriter
import javax.inject.Inject
@@ -235,6 +235,7 @@ class DumpHandler @Inject constructor(
pw.println("$ <invocation> buffers")
pw.println("$ <invocation> bugreport-critical")
pw.println("$ <invocation> bugreport-normal")
+ pw.println("$ <invocation> config")
pw.println()
pw.println("Targets can be listed:")
@@ -313,13 +314,21 @@ 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"
}
}
private val PRIORITY_OPTIONS =
arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
-private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
+private val COMMANDS = arrayOf(
+ "bugreport-critical",
+ "bugreport-normal",
+ "buffers",
+ "dumpables",
+ "config",
+ "help"
+)
private class ParsedArgs(
val rawArgs: Array<String>,
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index cca04da8f426..dbca65122fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,7 +18,7 @@ package com.android.systemui.dump
import android.util.ArrayMap
import com.android.systemui.Dumpable
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 0eab1afc4119..8299b13d305f 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -19,7 +19,7 @@ package com.android.systemui.dump
import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.util.io.Files
import com.android.systemui.util.time.SystemClock
import java.io.IOException
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index ab1e8c6010d1..07606c1f015f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -45,35 +45,45 @@ public class Flags {
/***************************************/
// 100 - notification
+ // TODO(b/254512751): Tracking Bug
public static final UnreleasedFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
new UnreleasedFlag(103);
+ // TODO(b/254512732): Tracking Bug
public static final UnreleasedFlag NSSL_DEBUG_LINES =
new UnreleasedFlag(105);
+ // TODO(b/254512505): Tracking Bug
public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION =
new UnreleasedFlag(106);
- public static final UnreleasedFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE =
- new UnreleasedFlag(107);
-
+ // TODO(b/254512624): Tracking Bug
public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
+ // TODO(b/254512703): Tracking Bug
public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
new ReleasedFlag(109);
+ // TODO(b/254512517): Tracking Bug
public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD =
new UnreleasedFlag(110, true);
+ // TODO(b/254512538): Tracking Bug
public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
+ // TODO(b/254512425): Tracking Bug
public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
false);
+ // TODO(b/254512731): Tracking Bug
public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
- // next id: 114
+ 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
@@ -82,27 +92,33 @@ public class Flags {
// public static final BooleanFlag KEYGUARD_LAYOUT =
// new BooleanFlag(200, true);
+ // TODO(b/254512713): Tracking Bug
public static final ReleasedFlag LOCKSCREEN_ANIMATIONS =
new ReleasedFlag(201);
+ // TODO(b/254512750): Tracking Bug
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);
+ // TODO(b/254512281): Tracking Bug
public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
+ // TODO(b/254512694): Tracking Bug
public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
+ // TODO(b/254512676): Tracking Bug
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.
*/
+ // TODO(b/254512385): Tracking Bug
public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
/**
@@ -111,8 +127,9 @@ public class Flags {
* <p>If this is {@code false}, the interactor and repo skip the controller and directly access
* the framework APIs.
*/
- public static final ReleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- new ReleasedFlag(210);
+ // TODO(b/254513286): Tracking Bug
+ public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
+ new UnreleasedFlag(210);
/**
* Whether `UserSwitcherController` should use the user interactor.
@@ -123,24 +140,28 @@ public class Flags {
* <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 UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR = new UnreleasedFlag(211);
+ // TODO(b/254513102): Tracking Bug
+ 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(211);
+ public static final UnreleasedFlag STEP_CLOCK_ANIMATION = new UnreleasedFlag(212);
/***************************************/
// 300 - power menu
+ // TODO(b/254512600): Tracking Bug
public static final ReleasedFlag POWER_MENU_LITE =
new ReleasedFlag(300);
/***************************************/
// 400 - smartspace
+ // TODO(b/254513080): Tracking Bug
public static final ReleasedFlag SMARTSPACE_DEDUPING =
new ReleasedFlag(400);
+ // TODO(b/254513100): Tracking Bug
public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
new ReleasedFlag(401);
@@ -156,6 +177,7 @@ public class Flags {
public static final ReleasedFlag NEW_USER_SWITCHER =
new ReleasedFlag(500);
+ // TODO(b/254512321): Tracking Bug
public static final UnreleasedFlag COMBINED_QS_HEADERS =
new UnreleasedFlag(501, true);
@@ -168,37 +190,48 @@ public class Flags {
/**
* @deprecated Not needed anymore
*/
+ // TODO(b/254512699): Tracking Bug
@Deprecated
public static final ReleasedFlag NEW_FOOTER = new ReleasedFlag(504);
+ // TODO(b/254512747): Tracking Bug
public static final UnreleasedFlag NEW_HEADER = new UnreleasedFlag(505, true);
+ // TODO(b/254512383): Tracking Bug
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
+ // TODO(b/254512678): Tracking Bug
public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
/***************************************/
// 600- status bar
+ // TODO(b/254513246): Tracking Bug
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
+ // TODO(b/254513025): Tracking Bug
public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
new ReleasedFlag(603, false);
+ // TODO(b/254512623): Tracking Bug
public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
new UnreleasedFlag(604, false);
+ // TODO(b/254512660): Tracking Bug
public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
new UnreleasedFlag(605, false);
/***************************************/
// 700 - dialer/calls
+ // TODO(b/254512734): Tracking Bug
public static final ReleasedFlag ONGOING_CALL_STATUS_BAR_CHIP =
new ReleasedFlag(700);
+ // TODO(b/254512681): Tracking Bug
public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE =
new ReleasedFlag(701);
+ // TODO(b/254512753): Tracking Bug
public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
new ReleasedFlag(702);
@@ -209,33 +242,49 @@ public class Flags {
/***************************************/
// 801 - region sampling
+ // TODO(b/254512848): Tracking Bug
public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
// 802 - wallpaper rendering
+ // TODO(b/254512923): Tracking Bug
public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
// 803 - screen contents translation
+ // TODO(b/254513187): Tracking Bug
public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
+ // 804 - monochromatic themes
+ public static final UnreleasedFlag MONOCHROMATIC_THEMES = new UnreleasedFlag(804);
+
/***************************************/
// 900 - media
+ // TODO(b/254512697): Tracking Bug
public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
+ // TODO(b/254512502): Tracking Bug
public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
+ // TODO(b/254512726): Tracking Bug
public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
+ // TODO(b/254512695): Tracking Bug
public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
+ // TODO(b/254512654): Tracking Bug
public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
+ // TODO(b/254512673): Tracking Bug
public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
+ // TODO(b/254513168): Tracking Bug
public static final UnreleasedFlag UMO_SURFACE_RIPPLE = new UnreleasedFlag(907);
// 1000 - dock
public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
new ReleasedFlag(1000);
+
+ // TODO(b/254512444): Tracking Bug
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
- new UnreleasedFlag(1002, /* teamfood= */ true);
+ // TODO(b/254512758): Tracking Bug
+ public static final ReleasedFlag ROUNDED_BOX_RIPPLE = new ReleasedFlag(1002);
- public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true);
+ // TODO(b/254512525): Tracking Bug
+ public static final ReleasedFlag REFACTORED_DOCK_SETUP = new ReleasedFlag(1003);
// 1100 - windowing
@Keep
@@ -249,11 +298,13 @@ public class Flags {
public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true);
+ // TODO(b/254513207): Tracking Bug
@Keep
public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING =
new DeviceConfigBooleanFlag(1102, "record_task_content",
NAMESPACE_WINDOW_MANAGER, false, true);
+ // TODO(b/254512674): Tracking Bug
@Keep
public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
@@ -296,18 +347,23 @@ public class Flags {
public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
+ // TODO(b/254512728): Tracking Bug
public static final UnreleasedFlag NEW_BACK_AFFORDANCE =
new UnreleasedFlag(1203, false /* teamfood */);
// 1300 - screenshots
+ // TODO(b/254512719): Tracking Bug
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+ // TODO(b/254513155): Tracking Bug
public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
// 1400 - columbus
+ // TODO(b/254512756): Tracking Bug
public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
// 1500 - chooser
+ // TODO(b/254512507): Tracking Bug
public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
// 1600 - accessibility
@@ -317,6 +373,10 @@ public class Flags {
// 1700 - clipboard
public static final UnreleasedFlag CLIPBOARD_OVERLAY_REFACTOR = new UnreleasedFlag(1700);
+ // 1800 - shade container
+ public static final UnreleasedFlag LEAVE_SHADE_OPEN_FOR_BUGREPORT =
+ new UnreleasedFlag(1800, true);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
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..ea26c4dc8a65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -751,6 +751,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "keyguardGone");
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
mKeyguardDisplayManager.hide();
+ mUpdateMonitor.startBiometricWatchdog();
Trace.endSection();
}
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/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/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index 23072a231417..d8691c17f53d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -11,11 +11,14 @@
* 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.
+ * limitations under the License
*/
+package com.android.systemui.keyguard.shared.model
-package com.android.settingslib.spa.gallery
-
-import com.android.settingslib.spa.framework.DebugActivity
-
-class GalleryDebugActivity : DebugActivity()
+/** 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/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 5651399cb891..f9e341c8629a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,6 +19,9 @@ package com.android.systemui.log
import android.app.ActivityManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+
import javax.inject.Inject
@SysUISingleton
@@ -26,7 +29,7 @@ class LogBufferFactory @Inject constructor(
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
- /* limit the size of maxPoolSize for low ram (Go) devices */
+ /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
private fun adjustMaxSize(requestedMaxSize: Int): Int {
return if (ActivityManager.isLowRamDeviceStatic()) {
minOf(requestedMaxSize, 20) /* low ram max log size*/
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
index 7f1ad6d20c16..eeadf406060d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
@@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import javax.inject.Qualifier;
/**
- * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
+ * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
* {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
*/
@Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
index 7d1f1c2709fa..5cca1ab2abe7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
index 9ca0293fbd86..1d016d837b02 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
index 7c5f4025117f..c9f78bcdeef8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
index 08d969b5eb77..76d20bea4bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
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 28aa19e18e80..00bf2104b7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,11 +22,11 @@ import android.os.Looper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.log.LogcatEchoTracker;
-import com.android.systemui.log.LogcatEchoTrackerDebug;
-import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
+import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
+import com.android.systemui.plugins.log.LogcatEchoTrackerProd;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.util.Compile;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index 1d7ba94af4ed..90ced0291805 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index b03655a543f7..e5ac3e2e893b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
index c67d8bebe313..73690ab6c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 53963fc8d431..99ec05bc8d94 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
index 5c572e8ef554..1570d434bc62 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
index edab8c319f87..bf216c6991d2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index 75a34fc22c3c..8c904eab409e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
index b1c6dcfcb13b..6d91f0c97c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
index 20fc6ff445a6..26af4964f7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
index fcc184a317b8..61daf9c8d71c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
index 760fbf3928b6..a59afa0fed1b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
index a0b686487bec..6f8ea7ff2e9b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
index 8c8753a07339..835d3490293c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
index 7259eebf19b6..6e2bd7b2e1b5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
index e96e532f94bf..77b1bf5fd630 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
index 557a254e5c09..9fd166b759d2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
index dd5010cf39a8..dd168bac5654 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index bd0d298ebdee..d24bfcb88188 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
index b237f2d74483..67cdb722055b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index f26b3164f488..af0f7c518e64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
index dd6837563a74..4c276e2bfaab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
index 8671dbfdf1fe..ba8b27c23ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
index b1018f9544c0..d40624bfc63a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaCarouselControllerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A debug logger for [MediaCarouselController]. */
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/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index b52565d57f27..cc06b6c67879 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -33,7 +33,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
import com.android.systemui.util.time.SystemClock
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
index d9c58c0d0d76..8c9e2d88c694 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
@@ -18,11 +18,10 @@ package com.android.systemui.media
import android.media.session.PlaybackState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaTimeoutListenerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
-
private const val TAG = "MediaTimeout"
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
index 73868189b362..51c658cb6c54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaViewLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "MediaView"
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
index 41f735486c7e..a9c5c61dddbb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
@@ -18,9 +18,9 @@ package com.android.systemui.media
import android.content.ComponentName
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaBrowserLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A logger for events in [ResumeMediaBrowser]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index a8a84331050d..e15e2d3dcccf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,7 +17,6 @@
package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
import com.android.systemui.media.MediaDataManager;
@@ -33,6 +32,7 @@ import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
+import com.android.systemui.plugins.log.LogBuffer;
import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index 78f4e012da03..5ace3ea8a05b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -1,9 +1,9 @@
package com.android.systemui.media.muteawait
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaMuteAwaitLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 46b2cc141b3c..78408fce5a36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -1,9 +1,9 @@
package com.android.systemui.media.nearby
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NearbyMediaDevicesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b565f3c22f24..38c971ed3f7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.taptotransfer.common
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.temporarydisplay.TemporaryViewLogger
/**
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..fe2eed9c6079 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,89 @@ 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
+ }
+ }
+ }
+ )
+ }
+
+ /**
+ * 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/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 1ea93474f954..03503fd1ff61 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -17,10 +17,10 @@
package com.android.systemui.privacy.logging
import android.permission.PermissionGroupUsage
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.PrivacyLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.privacy.PrivacyDialog
import com.android.systemui.privacy.PrivacyItem
import java.text.SimpleDateFormat
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/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index e5d86cc96f25..025fb228b829 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.qs
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.QSFragmentDisableLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import javax.inject.Inject
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/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 4cacbbacec2f..5d03da3cc113 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
@@ -53,6 +54,7 @@ import javax.inject.Provider;
/**
* Runs the day-to-day operations of which tiles should be bound and when.
*/
+@SysUISingleton
public class TileServices extends IQSService.Stub {
static final int DEFAULT_MAX_BOUND = 3;
static final int REDUCED_MAX_BOUND = 1;
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/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 60380064e098..931dc8df151a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -17,12 +17,12 @@
package com.android.systemui.qs.logging
import android.service.quicksettings.Tile
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.StatusBarState
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index d2d5063c7ae0..b6b657ec82f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
@@ -43,6 +44,9 @@ import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
+import java.util.List;
+import java.util.stream.Collectors;
+
import javax.inject.Inject;
/**
@@ -83,6 +87,13 @@ public class UserDetailView extends PseudoGridView {
private final FalsingManager mFalsingManager;
private @Nullable UserSwitchDialogController.DialogShower mDialogShower;
+ @NonNull
+ @Override
+ protected List<UserRecord> getUsers() {
+ return super.getUsers().stream().filter(
+ userRecord -> !userRecord.isManageUsers).collect(Collectors.toList());
+ }
+
@Inject
public Adapter(Context context, UserSwitcherController controller,
UiEventLogger uiEventLogger, FalsingManager falsingManager) {
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/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index a494f42985ac..6b540aa9f392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -292,6 +292,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
+ v.pivotY = v.height.toFloat() / 2
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 07e8b9fe3123..754036d3baa9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -16,7 +16,7 @@ package com.android.systemui.shade
import android.view.MotionEvent
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
import java.text.SimpleDateFormat
import java.util.Locale
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 29d546fe78c5..8433ee8a7ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1443,6 +1443,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));
@@ -1675,9 +1685,9 @@ public final class NotificationPanelViewController {
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
boolean customClockAnimation =
- mKeyguardStatusViewController
- .getClockAnimations()
- .getHasCustomPositionUpdatedAnimation();
+ mKeyguardStatusViewController.getClockAnimations() != null
+ && mKeyguardStatusViewController.getClockAnimations()
+ .getHasCustomPositionUpdatedAnimation();
if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
// Find the clock, so we can exclude it from this transition.
@@ -2127,7 +2137,8 @@ public final class NotificationPanelViewController {
animator.start();
}
- private void onFlingEnd(boolean cancelled) {
+ @VisibleForTesting
+ void onFlingEnd(boolean cancelled) {
mIsFlinging = false;
// No overshoot when the animation ends
setOverExpansionInternal(0, false /* isFromGesture */);
@@ -3717,6 +3728,11 @@ public final class NotificationPanelViewController {
setListening(true);
}
+ @VisibleForTesting
+ void setTouchSlopExceeded(boolean isTouchSlopExceeded) {
+ mTouchSlopExceeded = isTouchSlopExceeded;
+ }
+
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
@@ -3868,12 +3884,14 @@ public final class NotificationPanelViewController {
}
}
- private void setIsClosing(boolean isClosing) {
+ @VisibleForTesting
+ void setIsClosing(boolean isClosing) {
boolean wasClosing = isClosing();
mClosing = isClosing;
if (wasClosing != isClosing) {
mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
}
+ mAmbientState.setIsClosing(isClosing);
}
private void updateDozingVisibilities(boolean animate) {
@@ -3903,17 +3921,17 @@ public final class NotificationPanelViewController {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
- if (mUpdateMonitor.isFaceEnrolled()
- && !mUpdateMonitor.isFaceDetectionRunning()
- && !mUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser())) {
- mUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
- } else {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
+ 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,
+ 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()) {
@@ -4664,14 +4682,16 @@ public final class NotificationPanelViewController {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
- private void notifyExpandingStarted() {
+ @VisibleForTesting
+ void notifyExpandingStarted() {
if (!mExpanding) {
mExpanding = true;
onExpandingStarted();
}
}
- private void notifyExpandingFinished() {
+ @VisibleForTesting
+ void notifyExpandingFinished() {
endClosing();
if (mExpanding) {
mExpanding = false;
@@ -4771,6 +4791,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();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7bee0ba17afc..2b788d85a14c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -1,10 +1,10 @@
package com.android.systemui.shade
import android.view.MotionEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.ShadeLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -12,64 +12,69 @@ private const val TAG = "systemui.shade"
/** Lightweight logging utility for the Shade. */
class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
- fun v(@CompileTimeConstant msg: String) {
- buffer.log(TAG, LogLevel.VERBOSE, msg)
- }
+ fun v(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.VERBOSE, msg)
+ }
- private inline fun log(
- logLevel: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
- ) {
- buffer.log(TAG, logLevel, initializer, printer)
- }
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
- fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
- log(
- LogLevel.VERBOSE,
- { double1 = h.toDouble() },
- { "onQsIntercept: move action, QS tracking enabled. h = $double1" })
- }
+ fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
+ log(
+ LogLevel.VERBOSE,
+ { double1 = h.toDouble() },
+ { "onQsIntercept: move action, QS tracking enabled. h = $double1" }
+ )
+ }
- fun logQsTrackingNotStarted(
- initialTouchY: Float,
- y: Float,
- h: Float,
- touchSlop: Float,
- qsExpanded: Boolean,
- collapsedOnDown: Boolean,
- keyguardShowing: Boolean,
- qsExpansionEnabled: Boolean
- ) {
- log(
- LogLevel.VERBOSE,
- {
- int1 = initialTouchY.toInt()
- int2 = y.toInt()
- long1 = h.toLong()
- double1 = touchSlop.toDouble()
- bool1 = qsExpanded
- bool2 = collapsedOnDown
- bool3 = keyguardShowing
- bool4 = qsExpansionEnabled
- },
- {
- "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded=" +
- "$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4"
- })
- }
+ fun logQsTrackingNotStarted(
+ initialTouchY: Float,
+ y: Float,
+ h: Float,
+ touchSlop: Float,
+ qsExpanded: Boolean,
+ collapsedOnDown: Boolean,
+ keyguardShowing: Boolean,
+ qsExpansionEnabled: Boolean
+ ) {
+ log(
+ LogLevel.VERBOSE,
+ {
+ int1 = initialTouchY.toInt()
+ int2 = y.toInt()
+ long1 = h.toLong()
+ double1 = touchSlop.toDouble()
+ bool1 = qsExpanded
+ bool2 = collapsedOnDown
+ bool3 = keyguardShowing
+ bool4 = qsExpansionEnabled
+ },
+ {
+ "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" +
+ "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4"
+ }
+ )
+ }
- fun logMotionEvent(event: MotionEvent, message: String) {
- log(
- LogLevel.VERBOSE,
- {
- str1 = message
- long1 = event.eventTime
- long2 = event.downTime
- int1 = event.action
- int2 = event.classification
- double1 = event.y.toDouble()
- },
- { "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2" })
- }
+ fun logMotionEvent(event: MotionEvent, message: String) {
+ log(
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ },
+ {
+ "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+ }
+ )
+ }
}
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/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index 7f7ff9cf4881..90c52bd8c9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar
import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index ea7ec4f7fc39..450b757295bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -71,9 +71,9 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
index 17feaa842165..9bdff928c44b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.gesture
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index dfba8cd4a081..fc984618f1b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -119,6 +119,7 @@ class LockscreenSmartspaceController @Inject constructor(
regionSamplingEnabled,
updateFun
)
+ initializeTextColors(regionSamplingInstance)
regionSamplingInstance.startRegionSampler()
regionSamplingInstances.put(v, regionSamplingInstance)
connectSession()
@@ -362,18 +363,20 @@ class LockscreenSmartspaceController @Inject constructor(
}
}
+ private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) {
+ val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
+ val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
+
+ val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
+ val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
+
+ regionSamplingInstance.setForegroundColors(lightColor, darkColor)
+ }
+
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness()
- val themeID = if (isRegionDark.isDark) {
- R.style.Theme_SystemUI
- } else {
- R.style.Theme_SystemUI_LightWallpaper
- }
- val themedContext = ContextThemeWrapper(context, themeID)
- val wallpaperTextColor =
- Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor)
- it.setPrimaryTextColor(wallpaperTextColor)
+ val textColor = regionSamplingInstances.getValue(it).currentForegroundColor()
+ it.setPrimaryTextColor(textColor)
}
}
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 7fbdd35796c1..36b8333688ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -17,30 +17,14 @@
package com.android.systemui.statusbar.notification
import android.content.Context
-import android.util.Log
-import android.widget.Toast
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.util.Compile
import javax.inject.Inject
class NotifPipelineFlags @Inject constructor(
val context: Context,
val featureFlags: FeatureFlags
) {
- fun checkLegacyPipelineEnabled(): Boolean {
- if (Compile.IS_DEBUG) {
- Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
- }
- if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) {
- throw RuntimeException("Old pipeline code running with new pipeline enabled")
- } else {
- Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled",
- Exception())
- }
- return false
- }
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
@@ -53,4 +37,12 @@ class NotifPipelineFlags @Inject constructor(
fun fullScreenIntentRequiresKeyguard(): Boolean =
featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
+
+ val isStabilityIndexFixEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
+ }
+
+ val isSemiStableSortEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index ad3dfedcdb96..3058fbbc1031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index f8449ae8807b..84ab0d1190f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,6 +68,9 @@ data class ListAttachState private constructor(
*/
var stableIndex: Int = -1
+ /** Access the index of the [section] or -1 if the entry does not have one */
+ val sectionIndex: Int get() = section?.index ?: -1
+
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
@@ -95,11 +98,13 @@ data class ListAttachState private constructor(
* This can happen if the entry is removed from a group that was broken up or if the entry was
* filtered out during any of the filtering steps.
*/
- fun detach() {
+ fun detach(includingStableIndex: Boolean) {
parent = null
section = null
promoter = null
- // stableIndex = -1 // TODO(b/241229236): Clear this once we fix the stability fragility
+ if (includingStableIndex) {
+ stableIndex = -1
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index e129ee45817a..3ae2545e4e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -54,6 +54,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
@@ -96,11 +99,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
// TODO replace temp with collection pool for readability
private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
+ private NotifPipelineFlags mFlags;
private final boolean mAlwaysLogList;
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
+ private final SemiStableSort mSemiStableSort = new SemiStableSort();
+ private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;
private final PipelineState mPipelineState = new PipelineState();
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
@@ -141,6 +147,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
) {
mSystemClock = systemClock;
mLogger = logger;
+ mFlags = flags;
mAlwaysLogList = flags.isDevLoggingEnabled();
mInteractionTracker = interactionTracker;
mChoreographer = pipelineChoreographer;
@@ -527,7 +534,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
List<NotifFilter> filters) {
Trace.beginSection("ShadeListBuilder.filterNotifs");
final long now = mSystemClock.uptimeMillis();
- for (ListEntry entry : entries) {
+ for (ListEntry entry : entries) {
if (entry instanceof GroupEntry) {
final GroupEntry groupEntry = (GroupEntry) entry;
@@ -958,7 +965,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
- entry.getAttachState().detach();
+ // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility
+ entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());
}
private void assignSections() {
@@ -978,7 +986,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private void sortListAndGroups() {
Trace.beginSection("ShadeListBuilder.sortListAndGroups");
- // Assign sections to top-level elements and sort their children
+ if (mFlags.isSemiStableSortEnabled()) {
+ sortWithSemiStableSort();
+ } else {
+ sortWithLegacyStability();
+ }
+ Trace.endSection();
+ }
+
+ private void sortWithLegacyStability() {
+ // Sort all groups and the top level list
for (ListEntry entry : mNotifList) {
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
@@ -991,16 +1008,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// Check for suppressed order changes
if (!getStabilityManager().isEveryChangeAllowed()) {
mForceReorderable = true;
- boolean isSorted = isShadeSorted();
+ boolean isSorted = isShadeSortedLegacy();
mForceReorderable = false;
if (!isSorted) {
getStabilityManager().onEntryReorderSuppressed();
}
}
- Trace.endSection();
}
- private boolean isShadeSorted() {
+ private boolean isShadeSortedLegacy() {
if (!isSorted(mNotifList, mTopLevelComparator)) {
return false;
}
@@ -1014,6 +1030,43 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
return true;
}
+ private void sortWithSemiStableSort() {
+ // Sort each group's children
+ boolean allSorted = true;
+ for (ListEntry entry : mNotifList) {
+ if (entry instanceof GroupEntry) {
+ GroupEntry parent = (GroupEntry) entry;
+ allSorted &= sortGroupChildren(parent.getRawChildren());
+ }
+ }
+ // Sort each section within the top level list
+ mNotifList.sort(mTopLevelComparator);
+ if (!getStabilityManager().isEveryChangeAllowed()) {
+ for (List<ListEntry> subList : getSectionSubLists(mNotifList)) {
+ allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList);
+ }
+ applyNewNotifList();
+ }
+ assignIndexes(mNotifList);
+ if (!allSorted) {
+ // Report suppressed order changes
+ getStabilityManager().onEntryReorderSuppressed();
+ }
+ }
+
+ private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) {
+ return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries);
+ }
+
+ private boolean sortGroupChildren(List<NotificationEntry> entries) {
+ if (getStabilityManager().isEveryChangeAllowed()) {
+ entries.sort(mGroupChildrenComparator);
+ return true;
+ } else {
+ return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator);
+ }
+ }
+
/** Determine whether the items in the list are sorted according to the comparator */
@VisibleForTesting
public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
@@ -1036,27 +1089,41 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
/**
* Assign the index of each notification relative to the total order
*/
- private static void assignIndexes(List<ListEntry> notifList) {
+ private void assignIndexes(List<ListEntry> notifList) {
if (notifList.size() == 0) return;
NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
int sectionMemberIndex = 0;
for (int i = 0; i < notifList.size(); i++) {
- ListEntry entry = notifList.get(i);
+ final ListEntry entry = notifList.get(i);
NotifSection section = requireNonNull(entry.getSection());
if (section.getIndex() != currentSection.getIndex()) {
sectionMemberIndex = 0;
currentSection = section;
}
- entry.getAttachState().setStableIndex(sectionMemberIndex);
- if (entry instanceof GroupEntry) {
- GroupEntry parent = (GroupEntry) entry;
- for (int j = 0; j < parent.getChildren().size(); j++) {
- entry = parent.getChildren().get(j);
- entry.getAttachState().setStableIndex(sectionMemberIndex);
- sectionMemberIndex++;
+ if (mFlags.isStabilityIndexFixEnabled()) {
+ entry.getAttachState().setStableIndex(sectionMemberIndex++);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parent = (GroupEntry) entry;
+ final NotificationEntry summary = parent.getSummary();
+ if (summary != null) {
+ summary.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
+ for (NotificationEntry child : parent.getChildren()) {
+ child.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
+ }
+ } else {
+ // This old implementation uses the same index number for the group as the first
+ // child, and fails to assign an index to the summary. Remove once tested.
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parent = (GroupEntry) entry;
+ for (NotificationEntry child : parent.getChildren()) {
+ child.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
}
+ sectionMemberIndex++;
}
- sectionMemberIndex++;
}
}
@@ -1196,7 +1263,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
o2.getSectionIndex());
if (cmp != 0) return cmp;
- cmp = Integer.compare(
+ cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
getStableOrderIndex(o1),
getStableOrderIndex(o2));
if (cmp != 0) return cmp;
@@ -1225,7 +1292,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int cmp = Integer.compare(
+ int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
getStableOrderIndex(o1),
getStableOrderIndex(o2));
if (cmp != 0) return cmp;
@@ -1256,9 +1323,25 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// let the stability manager constrain or allow reordering
return -1;
}
+ // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility
return entry.getPreviousAttachState().getStableIndex();
}
+ @Nullable
+ private Integer getStableOrderRank(ListEntry entry) {
+ if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+ // let the stability manager constrain or allow reordering
+ return null;
+ }
+ if (entry.getAttachState().getSectionIndex()
+ != entry.getPreviousAttachState().getSectionIndex()) {
+ // stable index is only valid within the same section; otherwise we allow reordering
+ return null;
+ }
+ final int stableIndex = entry.getPreviousAttachState().getStableIndex();
+ return stableIndex == -1 ? null : stableIndex;
+ }
+
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
final NotifFilter filter = findRejectingFilter(entry, now, filters);
entry.getAttachState().setExcludingFilter(filter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 211e37473a70..68d1319699d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coalescer
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index e8f352f60da0..2919def16304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.row.NotificationGuts
import javax.inject.Inject
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/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 8625cdbc89d5..dfaa291c6bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -1,9 +1,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.util.Log
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "HeadsUpCoordinator"
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 93146f9b3bf3..6e76691ae1b1 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,10 +407,7 @@ public class PreparationCoordinator implements Coordinator {
mLogger.logGroupInflationTookTooLong(group);
return false;
}
- // 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 (!isInflated(group.getSummary())) {
+ if (mInflatingNotifs.contains(group.getSummary())) {
mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index c4f4ed54e2fa..9558f47af795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index c687e1bacbc9..d80445491bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
new file mode 100644
index 000000000000..9ec8e07e73b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.notification.collection.listbuilder
+
+import androidx.annotation.VisibleForTesting
+import kotlin.math.sign
+
+class SemiStableSort {
+ val preallocatedWorkspace by lazy { ArrayList<Any>() }
+ val preallocatedAdditions by lazy { ArrayList<Any>() }
+ val preallocatedMapToIndex by lazy { HashMap<Any, Int>() }
+ val preallocatedMapToIndexComparator: Comparator<Any> by lazy {
+ Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 }
+ }
+
+ /**
+ * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+ * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+ * items will be combined to have the fewest elements out of order according to the [comparator]
+ * . The result will be placed into the original [items] list.
+ */
+ fun <T : Any> sort(
+ items: MutableList<T>,
+ stableOrder: StableOrder<in T>,
+ comparator: Comparator<in T>,
+ ): Boolean =
+ withWorkspace<T, Boolean> { workspace ->
+ val ordered =
+ sortTo(
+ items,
+ stableOrder,
+ comparator,
+ workspace,
+ )
+ items.clear()
+ items.addAll(workspace)
+ return ordered
+ }
+
+ /**
+ * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+ * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+ * items will be combined to have the fewest elements out of order according to the [comparator]
+ * . The result will be put into [output].
+ */
+ fun <T : Any> sortTo(
+ items: Iterable<T>,
+ stableOrder: StableOrder<in T>,
+ comparator: Comparator<in T>,
+ output: MutableList<T>,
+ ): Boolean {
+ if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}")
+ // If array already has elements, use subList to ensure we only append
+ val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+ items.filterTo(result) { stableOrder.getRank(it) != null }
+ result.sortBy { stableOrder.getRank(it)!! }
+ val isOrdered = result.isSorted(comparator)
+ withAdditions<T> { additions ->
+ items.filterTo(additions) { stableOrder.getRank(it) == null }
+ additions.sortWith(comparator)
+ insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+ }
+ return isOrdered
+ }
+
+ /**
+ * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the
+ * result in [output]. Items with a [stableOrder] will be in that order, items without a
+ * [stableOrder] will remain in same relative order as the input, and the two sets of items will
+ * be combined to have the fewest elements moved from their locations in the original.
+ */
+ fun <T : Any> stabilizeTo(
+ sortedItems: Iterable<T>,
+ stableOrder: StableOrder<in T>,
+ output: MutableList<T>,
+ ): Boolean {
+ // Append to the output array if present
+ val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+ sortedItems.filterTo(result) { stableOrder.getRank(it) != null }
+ val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! }
+ val isOrdered = result.isSorted(stableRankComparator)
+ if (!isOrdered) {
+ result.sortWith(stableRankComparator)
+ }
+ if (result.isEmpty()) {
+ sortedItems.filterTo(result) { stableOrder.getRank(it) == null }
+ return isOrdered
+ }
+ withAdditions<T> { additions ->
+ sortedItems.filterTo(additions) { stableOrder.getRank(it) == null }
+ if (additions.isNotEmpty()) {
+ withIndexOfComparator(sortedItems) { comparator ->
+ insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+ }
+ }
+ }
+ return isOrdered
+ }
+
+ private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R {
+ preallocatedWorkspace.clear()
+ val result = block(preallocatedWorkspace as ArrayList<T>)
+ preallocatedWorkspace.clear()
+ return result
+ }
+
+ private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) {
+ preallocatedAdditions.clear()
+ block(preallocatedAdditions as ArrayList<T>)
+ preallocatedAdditions.clear()
+ }
+
+ private inline fun <T : Any> withIndexOfComparator(
+ sortedItems: Iterable<T>,
+ block: (Comparator<in T>) -> Unit
+ ) {
+ preallocatedMapToIndex.clear()
+ sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i }
+ block(preallocatedMapToIndexComparator as Comparator<in T>)
+ preallocatedMapToIndex.clear()
+ }
+
+ companion object {
+
+ /**
+ * This is the core of the algorithm.
+ *
+ * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without
+ * changing the relative order of any elements already in [existing], even though those
+ * elements may be mis-ordered relative to the [comparator], such that the total number of
+ * elements which are ordered incorrectly according to the [comparator] is fewest.
+ */
+ private fun <T> insertPreSortedElementsWithFewestMisOrderings(
+ existing: MutableList<T>,
+ preSortedAdditions: Iterable<T>,
+ comparator: Comparator<in T>,
+ ) {
+ if (DEBUG) println(" To $existing insert $preSortedAdditions with fewest misordering")
+ var iStart = 0
+ preSortedAdditions.forEach { toAdd ->
+ if (DEBUG) println(" need to add $toAdd to $existing, starting at $iStart")
+ var cmpSum = 0
+ var cmpSumMax = 0
+ var iCmpSumMax = iStart
+ if (DEBUG) print(" ")
+ for (i in iCmpSumMax until existing.size) {
+ val cmp = comparator.compare(toAdd, existing[i]).sign
+ cmpSum += cmp
+ if (cmpSum > cmpSumMax) {
+ cmpSumMax = cmpSum
+ iCmpSumMax = i + 1
+ }
+ if (DEBUG) print("sum[$i]=$cmpSum, ")
+ }
+ if (DEBUG) println("inserting $toAdd at $iCmpSumMax")
+ existing.add(iCmpSumMax, toAdd)
+ iStart = iCmpSumMax + 1
+ }
+ }
+
+ /** Determines if a list is correctly sorted according to the given comparator */
+ @VisibleForTesting
+ fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean {
+ if (this.size <= 1) {
+ return true
+ }
+ val iterator = this.iterator()
+ var previous = iterator.next()
+ var current: T?
+ while (iterator.hasNext()) {
+ current = iterator.next()
+ if (comparator.compare(previous, current) > 0) {
+ return false
+ }
+ previous = current
+ }
+ return true
+ }
+ }
+
+ fun interface StableOrder<T> {
+ fun getRank(item: T): Int?
+ }
+}
+
+val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
new file mode 100644
index 000000000000..d8f75f61c05a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.notification.collection.listbuilder
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+
+object ShadeListBuilderHelper {
+ fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> =
+ getContiguousSubLists(entries, minLength = 1) { it.sectionIndex }
+
+ inline fun <T : Any, K : Any> getContiguousSubLists(
+ itemList: List<T>,
+ minLength: Int = 1,
+ key: (T) -> K,
+ ): Iterable<List<T>> {
+ val subLists = mutableListOf<List<T>>()
+ val numEntries = itemList.size
+ var currentSectionStartIndex = 0
+ var currentSectionKey: K? = null
+ for (i in 0 until numEntries) {
+ val sectionKey = key(itemList[i])
+ if (currentSectionKey == null) {
+ currentSectionKey = sectionKey
+ } else if (currentSectionKey != sectionKey) {
+ val length = i - currentSectionStartIndex
+ if (length >= minLength) {
+ subLists.add(itemList.subList(currentSectionStartIndex, i))
+ }
+ currentSectionStartIndex = i
+ currentSectionKey = sectionKey
+ }
+ }
+ val length = numEntries - currentSectionStartIndex
+ if (length >= minLength) {
+ subLists.add(itemList.subList(currentSectionStartIndex, numEntries))
+ }
+ return subLists
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index d8dae5d23f42..8e052c7dcc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index aa27e1e407f0..911a2d0c2b36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -20,13 +20,13 @@ import android.os.RemoteException
import android.service.notification.NotificationListenerService
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogLevel.WTF
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 38e3d496a60c..9c71e5c1054c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 6d1071c283e3..b4b9438cd6be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import java.lang.RuntimeException
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 5dbec8dcba20..d4f11fc141f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.interruption
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 99d320d1c7ca..073b6b041b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.interruption
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index fe03b2ad6a32..10197a38527e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.logging
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationSection
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index ab91926d466a..46fef3f973a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.row
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index f9923b2254d7..8a5d29a1ae2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.row
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 2719dd88b7be..b2628e40e77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -142,6 +142,11 @@ public class AmbientState implements Dumpable {
*/
private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false;
+ /**
+ * Whether the shade is currently closing.
+ */
+ private boolean mIsClosing;
+
@VisibleForTesting
public boolean isFlingRequiredAfterLockScreenSwipeUp() {
return mIsFlingRequiredAfterLockScreenSwipeUp;
@@ -717,6 +722,20 @@ public class AmbientState implements Dumpable {
&& mStatusBarKeyguardViewManager.isBouncerInTransit();
}
+ /**
+ * @param isClosing Whether the shade is currently closing.
+ */
+ public void setIsClosing(boolean isClosing) {
+ mIsClosing = isClosing;
+ }
+
+ /**
+ * @return Whether the shade is currently closing.
+ */
+ public boolean isClosing() {
+ return mIsClosing;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("mTopPadding=" + mTopPadding);
@@ -761,5 +780,6 @@ public class AmbientState implements Dumpable {
+ mIsFlingRequiredAfterLockScreenSwipeUp);
pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements);
pw.println("mBaseZHeight=" + mBaseZHeight);
+ pw.println("mIsClosing=" + mIsClosing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index cb7dfe87f7fb..b61c55edadcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.notification.stack
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationSectionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "NotifSections"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 55c577f1ea39..2272411b4314 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -255,7 +255,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mClearAllInProgress;
private FooterClearAllListener mFooterClearAllListener;
private boolean mFlingAfterUpEvent;
-
/**
* Was the scroller scrolled to the top when the down motion was observed?
*/
@@ -4020,8 +4019,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
setOwnScrollY(0);
}
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private void setIsExpanded(boolean isExpanded) {
+ void setIsExpanded(boolean isExpanded) {
boolean changed = isExpanded != mIsExpanded;
mIsExpanded = isExpanded;
mStackScrollAlgorithm.setIsExpanded(isExpanded);
@@ -4842,13 +4842,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private void setOwnScrollY(int ownScrollY) {
+ void setOwnScrollY(int ownScrollY) {
setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+ // Avoid Flicking during clear all
+ // when the shade finishes closing, onExpansionStopped will call
+ // resetScrollPosition to setOwnScrollY to 0
+ if (mAmbientState.isClosing()) {
+ return;
+ }
+
if (ownScrollY != mOwnScrollY) {
// We still want to call the normal scrolled changed for accessibility reasons
onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 5f79c0e3913a..4c52db7f8732 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.stack
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index cb4a0884fea4..f5de678a8536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.stack
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
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..3d5e37376fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -455,6 +455,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..709a434c08ac 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();
@@ -3561,6 +3566,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/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 02b235493715..4839fe6a7bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone
import android.util.DisplayMetrics
import android.view.View
import com.android.internal.logging.nano.MetricsProto.MetricsEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.LSShadeTransitionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import javax.inject.Inject
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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5f5ec68ba898..30591d08075a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1094,7 +1094,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 {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index b9a1413ff791..81edff45c505 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -17,12 +17,12 @@
package com.android.systemui.statusbar.phone
import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index 28ed0806a181..d64bc58a0c37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone.fragment
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.CollapsedSbFragmentLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import javax.inject.Inject
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/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index dbb1aa54d8ee..d3cf32fb44ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,10 +18,10 @@ package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 2f0ebf752a23..28a9b97b8ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -43,11 +43,7 @@ protected constructor(
}
override fun getCount(): Int {
- return if (controller.isKeyguardShowing) {
- users.count { !it.isRestricted }
- } else {
- users.size
- }
+ return users.size
}
override fun getItem(position: Int): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index d7c81af53d8b..df1e80b78c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -16,10 +16,10 @@
package com.android.systemui.statusbar.policy
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
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/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 606a11a84686..a7185cb18c40 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -16,8 +16,8 @@
package com.android.systemui.temporarydisplay
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
open class TemporaryViewLogger(
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..45ce687a1a4d 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,24 @@ 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.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 +76,10 @@ 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>(
+) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -104,15 +101,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 +115,49 @@ 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()
- // Failure
- currentView.requireViewById<View>(R.id.failure_icon).visibility =
- (chipState.transferStatus == TransferStatus.FAILED).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)
+ }
- // For accessibility
+ buttonView.setOnClickListener(onClickListener)
+ buttonView.visibility = View.VISIBLE
+ } else {
+ buttonView.visibility = View.GONE
+ }
+
+ // ---- 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)}"
}
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 +172,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 +194,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..211a66387966
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.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).
+ */
+data class ChipbarInfo(
+ val startIcon: Icon,
+ val text: Text,
+ val endItem: ChipbarEndItem?,
+) : 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/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 51541bd3032e..fda511433143 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.toast
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.ToastLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogMessage
import javax.inject.Inject
private const val TAG = "ToastLog"
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 919e699652bc..d768b6dc195a 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
@@ -321,6 +321,7 @@ constructor(
return when {
isAddUser -> false
isAddSupervisedUser -> false
+ isManageUsers -> false
isGuest -> info != null
else -> true
}
@@ -346,6 +347,7 @@ constructor(
isAddUser -> UserActionModel.ADD_USER
isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
isGuest -> UserActionModel.ENTER_GUEST_MODE
+ isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
else -> error("Don't know how to convert to UserActionModel: $this")
}
}
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 ba5a82a42d94..0d5c64b83e6e 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
@@ -236,18 +236,7 @@ constructor(
}
.flatMapLatest { isActionable ->
if (isActionable) {
- repository.actions.map { actions ->
- actions +
- if (actions.isNotEmpty()) {
- // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
- // because that's a user switcher specific action that is
- // not known to the our data source or other features.
- listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- } else {
- // If no actions, don't add the navigate action.
- emptyList()
- }
- }
+ repository.actions
} else {
// If not actionable it means that we're not allowed to show actions
// when
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 91c592177d19..f7e19c0ca810 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
@@ -30,6 +30,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
@@ -41,19 +42,19 @@ import kotlinx.coroutines.launch
class UserSwitcherDialogCoordinator
@Inject
constructor(
- @Application private val context: Context,
- @Application private val applicationScope: CoroutineScope,
- private val falsingManager: FalsingManager,
- private val broadcastSender: BroadcastSender,
- private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val interactor: UserInteractor,
- private val featureFlags: FeatureFlags,
+ @Application private val context: Lazy<Context>,
+ @Application private val applicationScope: Lazy<CoroutineScope>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val broadcastSender: Lazy<BroadcastSender>,
+ private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
+ private val interactor: Lazy<UserInteractor>,
+ private val featureFlags: Lazy<FeatureFlags>,
) : CoreStartable {
private var currentDialog: Dialog? = null
override fun start() {
- if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+ if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
return
}
@@ -62,8 +63,8 @@ constructor(
}
private fun startHandlingDialogShowRequests() {
- applicationScope.launch {
- interactor.dialogShowRequests.filterNotNull().collect { request ->
+ applicationScope.get().launch {
+ interactor.get().dialogShowRequests.filterNotNull().collect { request ->
currentDialog?.let {
if (it.isShowing) {
it.cancel()
@@ -74,48 +75,48 @@ constructor(
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
AddUserDialog(
- context = context,
+ context = context.get(),
userHandle = request.userHandle,
isKeyguardShowing = request.isKeyguardShowing,
showEphemeralMessage = request.showEphemeralMessage,
- falsingManager = falsingManager,
- broadcastSender = broadcastSender,
- dialogLaunchAnimator = dialogLaunchAnimator,
+ falsingManager = falsingManager.get(),
+ broadcastSender = broadcastSender.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
)
is ShowDialogRequestModel.ShowUserCreationDialog ->
UserCreatingDialog(
- context,
+ context.get(),
request.isGuest,
)
is ShowDialogRequestModel.ShowExitGuestDialog ->
ExitGuestDialog(
- context = context,
+ context = context.get(),
guestUserId = request.guestUserId,
isGuestEphemeral = request.isGuestEphemeral,
targetUserId = request.targetUserId,
isKeyguardShowing = request.isKeyguardShowing,
- falsingManager = falsingManager,
- dialogLaunchAnimator = dialogLaunchAnimator,
+ falsingManager = falsingManager.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
onExitGuestUserListener = request.onExitGuestUser,
)
}
currentDialog?.show()
- interactor.onDialogShown()
+ interactor.get().onDialogShown()
}
}
}
private fun startHandlingDialogDismissRequests() {
- applicationScope.launch {
- interactor.dialogDismissRequests.filterNotNull().collect {
+ applicationScope.get().launch {
+ interactor.get().dialogDismissRequests.filterNotNull().collect {
currentDialog?.let {
if (it.isShowing) {
it.cancel()
}
}
- interactor.onDialogDismissed()
+ interactor.get().onDialogDismissed()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 219dae29117f..d857e85bac53 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -62,17 +62,7 @@ private constructor(
val isMenuVisible: Flow<Boolean> = _isMenuVisible
/** The user action menu. */
val menu: Flow<List<UserActionViewModel>> =
- userInteractor.actions.map { actions ->
- if (isNewImpl && actions.isNotEmpty()) {
- // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
- // switcher specific action that is not known to the our data source or other
- // features.
- actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- } else {
- actions
- }
- .map { action -> toViewModel(action) }
- }
+ userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
/** Whether the button to open the user action menu is visible. */
val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
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/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
new file mode 100644
index 000000000000..9d6aff219148
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BouncerKeyguardMessageAreaTest : SysuiTestCase() {
+ class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) :
+ BouncerKeyguardMessageArea(context, attrs) {
+ override val SHOW_DURATION_MILLIS = 0L
+ override val HIDE_DURATION_MILLIS = 0L
+ }
+ lateinit var underTest: BouncerKeyguardMessageArea
+
+ @Before
+ fun setup() {
+ underTest = FakeBouncerKeyguardMessageArea(context, null)
+ }
+
+ @Test
+ fun testSetSameMessage() {
+ val underTestSpy = spy(underTest)
+ underTestSpy.setMessage("abc")
+ underTestSpy.setMessage("abc")
+ verify(underTestSpy, times(1)).text = "abc"
+ }
+
+ @Test
+ fun testSetDifferentMessage() {
+ underTest.setMessage("abc")
+ underTest.setMessage("def")
+ assertThat(underTest.text).isEqualTo("def")
+ }
+
+ @Test
+ fun testSetNullMessage() {
+ 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/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 69524e5a4537..5d2b0ca4e7ea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -17,13 +17,11 @@
package com.android.keyguard;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -92,19 +90,4 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
}
-
- @Test
- public void testSetMessageIfEmpty_empty() {
- mMessageAreaController.setMessage("");
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin);
- }
-
- @Test
- public void testSetMessageIfEmpty_notEmpty() {
- mMessageAreaController.setMessage("abc");
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- verify(mKeyguardMessageArea, never()).setMessage(getContext()
- .getResources().getText(R.string.keyguard_enter_your_pin));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b89dbd98968a..b369098cafc0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -114,9 +114,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
}
@Test
- fun onResume_testSetInitialText() {
- keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON)
- verify(mKeyguardMessageAreaController)
- .setMessageIfEmpty(R.string.keyguard_enter_your_password)
+ fun startAppearAnimation() {
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 3262a77b7711..9eff70487c74 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -100,16 +100,16 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
}
@Test
- fun onPause_clearsTextField() {
+ fun onPause_resetsText() {
mKeyguardPatternViewController.init()
mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage("")
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
+
@Test
- fun onResume_setInitialText() {
- mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON)
- verify(mKeyguardMessageAreaController)
- .setMessageIfEmpty(R.string.keyguard_enter_your_pattern)
+ fun startAppearAnimation() {
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 97d556b04aa4..ce1101f389c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -113,11 +113,4 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
verify(mPasswordEntry).requestFocus();
}
-
- @Test
- public void onResume_setInitialText() {
- mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
- verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- }
}
-
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9e5bfe53ea05..d9efdeaea04c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -98,6 +98,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin)
+ verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
}
}
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 baeabc577fb7..cd50144bf2e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -163,6 +163,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
overlayParams = UdfpsOverlayParams(
sensorBounds,
+ sensorBounds,
DISPLAY_WIDTH,
DISPLAY_HEIGHT,
scaleFactor = 1f,
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 f210708806ab..eff47bd2ee98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -414,7 +414,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};
final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};
final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
- displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
+ sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
for (int i1 = 0; i1 <= 1; ++i1) {
for (int i2 = 0; i2 <= 1; ++i2) {
@@ -422,8 +422,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
for (int i4 = 0; i4 <= 1; ++i4) {
for (int i5 = 0; i5 <= 1; ++i5) {
final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
- sensorBounds[i1], displayWidth[i2], displayHeight[i3],
- scaleFactor[i4], rotation[i5]);
+ sensorBounds[i1], sensorBounds[i1], displayWidth[i2],
+ displayHeight[i3], scaleFactor[i4], rotation[i5]);
if (newParams.equals(oldParams)) {
continue;
@@ -466,8 +466,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Initialize the overlay.
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- rotation));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, rotation));
// Show the overlay.
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
@@ -477,8 +477,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Update overlay with the same parameters.
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- rotation));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, rotation));
mFgExecutor.runAllReady();
// Ensure the overlay was not recreated.
@@ -525,8 +525,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_0
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- Surface.ROTATION_0));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
@@ -542,8 +542,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_90
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- Surface.ROTATION_90));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
@@ -558,8 +558,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_270
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- Surface.ROTATION_270));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
@@ -574,8 +574,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_180
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
- new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
- Surface.ROTATION_180));
+ new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
+ scaleFactor, Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index b78c06391057..ac936e1a77c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -68,7 +68,8 @@ class UdfpsViewTest : SysuiTestCase() {
view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
view.animationViewController = animationViewController
val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect
- view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0)
+ view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920,
+ 1080, 1f, Surface.ROTATION_0)
view.setUdfpsDisplayModeProvider(hbmProvider)
ViewUtils.attachView(view)
}
@@ -133,7 +134,8 @@ class UdfpsViewTest : SysuiTestCase() {
@Test
fun isNotWithinSensorArea() {
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
- assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse()
+ assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat()))
+ .isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
}
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/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 6a55a60c2fda..5bbd8109d8f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.doze;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
@@ -38,16 +41,17 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.UiModeManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -78,25 +82,30 @@ public class DozeMachineTest extends SysuiTestCase {
@Mock
private DozeHost mHost;
@Mock
- private UiModeManager mUiModeManager;
+ private DozeMachine.Part mPartMock;
+ @Mock
+ private DozeMachine.Part mAnotherPartMock;
private DozeServiceFake mServiceFake;
private WakeLockFake mWakeLockFake;
- private AmbientDisplayConfiguration mConfigMock;
- private DozeMachine.Part mPartMock;
+ private AmbientDisplayConfiguration mAmbientDisplayConfigMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mServiceFake = new DozeServiceFake();
mWakeLockFake = new WakeLockFake();
- mConfigMock = mock(AmbientDisplayConfiguration.class);
- mPartMock = mock(DozeMachine.Part.class);
+ mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);
when(mDockManager.isDocked()).thenReturn(false);
when(mDockManager.isHidden()).thenReturn(false);
- mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
- mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager,
- mHost, new DozeMachine.Part[]{mPartMock});
+ mMachine = new DozeMachine(mServiceFake,
+ mAmbientDisplayConfigMock,
+ mWakeLockFake,
+ mWakefulnessLifecycle,
+ mDozeLog,
+ mDockManager,
+ mHost,
+ new DozeMachine.Part[]{mPartMock, mAnotherPartMock});
}
@Test
@@ -108,7 +117,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
@@ -118,7 +127,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_goesToAod() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -138,7 +147,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_afterDockPaused_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -151,7 +160,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
@@ -162,7 +171,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -184,7 +193,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -197,7 +206,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -209,7 +218,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -222,7 +231,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_goesToAoD() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -236,7 +245,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -287,7 +296,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_afterDockPaused_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -303,7 +312,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -471,7 +480,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testTransitionToInitialized_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED);
@@ -481,7 +492,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testTransitionToFinish_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(FINISH);
@@ -490,7 +503,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeToDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE);
@@ -499,7 +514,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_AOD);
@@ -508,7 +525,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_PULSING_BRIGHT);
@@ -517,7 +536,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_AOD_DOCKED);
@@ -525,7 +546,35 @@ public class DozeMachineTest extends SysuiTestCase {
}
@Test
+ public void testOnConfigurationChanged_propagatesUiModeTypeToParts() {
+ Configuration newConfig = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(newConfig);
+
+ verify(mPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mAnotherPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ }
+
+ @Test
+ public void testOnConfigurationChanged_propagatesOnlyUiModeChangesToParts() {
+ Configuration newConfig = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(newConfig);
+ mMachine.onConfigurationChanged(newConfig);
+
+ verify(mPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mAnotherPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ }
+
+ @Test
public void testDozeSuppressTriggers_screenState() {
assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null));
}
+
+ @NonNull
+ private Configuration configWithCarNightUiMode() {
+ Configuration configuration = Configuration.EMPTY;
+ configuration.uiMode = UI_MODE_TYPE_CAR | UI_MODE_NIGHT_YES;
+ return configuration;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index 0f29dcd5a939..32b994538e12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -10,14 +10,14 @@
* 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 andatest
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.doze;
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
@@ -26,17 +26,16 @@ import static com.android.systemui.doze.DozeMachine.State.FINISH;
import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
@@ -44,13 +43,13 @@ import android.testing.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -71,10 +70,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Mock
private AmbientDisplayConfiguration mConfig;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private UiModeManager mUiModeManager;
- @Mock
private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@Mock
private BiometricUnlockController mBiometricUnlockController;
@@ -83,13 +78,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
private DozeMachine mDozeMachine;
@Captor
- private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
- @Captor
- private ArgumentCaptor<IntentFilter> mIntentFilterCaptor;
- private BroadcastReceiver mBroadcastReceiver;
- private IntentFilter mIntentFilter;
-
- @Captor
private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;
private DozeHost.Callback mDozeHostCallback;
@@ -106,8 +94,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
mDozeHost,
mConfig,
mDozeLog,
- mBroadcastDispatcher,
- mUiModeManager,
mBiometricUnlockControllerLazy);
mDozeSuppressor.setDozeMachine(mDozeMachine);
@@ -122,36 +108,35 @@ public class DozeSuppressorTest extends SysuiTestCase {
public void testRegistersListenersOnInitialized_unregisteredOnFinish() {
// check that receivers and callbacks registered
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
captureDozeHostCallback();
// check that receivers and callbacks are unregistered
mDozeSuppressor.transitionTo(INITIALIZED, FINISH);
- verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver);
verify(mDozeHost).removeCallback(mDozeHostCallback);
}
@Test
public void testSuspendTriggersDoze_carMode() {
// GIVEN car mode
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
// WHEN dozing begins
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
// THEN doze continues with all doze triggers disabled.
- verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, atLeastOnce()).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, never())
+ .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
}
@Test
public void testSuspendTriggersDoze_enterCarMode() {
// GIVEN currently dozing
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE);
// WHEN car mode entered
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
// THEN doze continues with all doze triggers disabled.
verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
@@ -160,13 +145,13 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Test
public void testDozeResume_exitCarMode() {
// GIVEN currently suspended, with AOD not enabled
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false);
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
// WHEN exiting car mode
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
// THEN doze is resumed
verify(mDozeMachine).requestState(DOZE);
@@ -175,19 +160,53 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Test
public void testDozeAoDResume_exitCarMode() {
// GIVEN currently suspended, with AOD not enabled
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
// WHEN exiting car mode
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
// THEN doze AOD is resumed
verify(mDozeMachine).requestState(DOZE_AOD);
}
@Test
+ public void testUiModeDoesNotChange_noStateTransition() {
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+ clearInvocations(mDozeMachine);
+
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+ verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, never())
+ .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
+ }
+
+ @Test
+ public void testUiModeTypeChange_whenDozeMachineIsNotReady_doesNotDoAnything() {
+ when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+ verify(mDozeMachine, never()).requestState(any());
+ }
+
+ @Test
+ public void testUiModeTypeChange_CarModeEnabledAndDozeMachineNotReady_suspendsTriggersAfter() {
+ when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mDozeMachine, never()).requestState(any());
+
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+ verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+ }
+
+ @Test
public void testEndDoze_unprovisioned() {
// GIVEN device unprovisioned
when(mDozeHost.isProvisioned()).thenReturn(false);
@@ -276,14 +295,4 @@ public class DozeSuppressorTest extends SysuiTestCase {
verify(mDozeHost).addCallback(mDozeHostCaptor.capture());
mDozeHostCallback = mDozeHostCaptor.getValue();
}
-
- private void captureBroadcastReceiver() {
- verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(),
- mIntentFilterCaptor.capture());
- mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
- mIntentFilter = mIntentFilterCaptor.getValue();
- assertEquals(2, mIntentFilter.countActions());
- org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(),
- containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE));
- }
}
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 fc672016a886..65b44a14d2ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.dump
import androidx.test.filters.SmallTest
+import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -30,6 +32,8 @@ 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() {
@@ -66,7 +70,9 @@ class DumpHandlerTest : SysuiTestCase() {
mContext,
dumpManager,
logBufferEulogizer,
- mutableMapOf(),
+ mutableMapOf(
+ EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
+ ),
exceptionHandlerManager
)
}
@@ -154,4 +160,20 @@ class DumpHandlerTest : SysuiTestCase() {
verify(buffer1).dump(pw, 0)
verify(buffer2).dump(pw, 0)
}
-} \ No newline at end of file
+
+ @Test
+ fun testConfigDump() {
+ // GIVEN a StringPrintWriter
+ val stringWriter = StringWriter()
+ val spw = PrintWriter(stringWriter)
+
+ // When a config dump is requested
+ dumpHandler.dump(spw, arrayOf("config"))
+
+ assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
+ }
+
+ private class EmptyCoreStartable : CoreStartable {
+ override fun start() {}
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index bd029a727ee3..64547f4463d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -16,9 +16,9 @@
package com.android.systemui.dump
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
/**
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
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/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index 1078cdaa57c4..e009e8651f2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.media.taptotransfer.common
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
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..f977f55db483 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,6 +17,9 @@
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.testing.AndroidTestingRunner
@@ -25,6 +28,7 @@ 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 +36,17 @@ 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.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 +65,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
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 +99,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,7 +126,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
accessibilityManager,
configurationController,
powerManager,
- uiEventLogger,
falsingManager,
falsingCollector,
viewUtil,
@@ -149,8 +174,15 @@ 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)
}
@@ -163,8 +195,15 @@ 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)
}
@@ -177,10 +216,15 @@ 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)
}
@@ -193,10 +237,15 @@ 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)
}
@@ -209,15 +258,69 @@ 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)
}
@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
fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
@@ -225,15 +328,71 @@ 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)
}
@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
fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
@@ -241,10 +400,15 @@ 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)
}
@@ -257,10 +421,15 @@ 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)
}
@@ -407,53 +576,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)
+ private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_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 transferToThisDeviceTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
+ private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
- /** 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 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/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index aacc695ef301..68c10f20f6f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
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/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/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index da52a9b1a3c2..bc27bbc13f81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -139,6 +140,11 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
clickableTest(false, false, mUserDetailItemView, true)
}
+ @Test
+ fun testManageUsersIsNotAvailable() {
+ assertNull(adapter.users.find { it.isManageUsers })
+ }
+
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
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/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 0151822f871c..14a3bc147808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -659,6 +659,51 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
verify(privacyIconsController, never()).onParentInvisible()
}
+ @Test
+ fun clockPivotYInCenter() {
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+ var height = 100
+ val width = 50
+
+ clock.executeLayoutChange(0, 0, width, height, captor.value)
+ verify(clock).pivotY = height.toFloat() / 2
+
+ height = 150
+ clock.executeLayoutChange(0, 0, width, height, captor.value)
+ verify(clock).pivotY = height.toFloat() / 2
+ }
+
+ private fun View.executeLayoutChange(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ listener: View.OnLayoutChangeListener
+ ) {
+ val oldLeft = this.left
+ val oldTop = this.top
+ val oldRight = this.right
+ val oldBottom = this.bottom
+ whenever(this.left).thenReturn(left)
+ whenever(this.top).thenReturn(top)
+ whenever(this.right).thenReturn(right)
+ whenever(this.bottom).thenReturn(bottom)
+ whenever(this.height).thenReturn(bottom - top)
+ whenever(this.width).thenReturn(right - left)
+ listener.onLayoutChange(
+ this,
+ oldLeft,
+ oldTop,
+ oldRight,
+ oldBottom,
+ left,
+ top,
+ right,
+ bottom
+ )
+ }
+
private fun createWindowInsets(
topCutout: Rect? = Rect()
): WindowInsets {
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 c0dae03023c5..e7138ab11cbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -33,11 +33,13 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
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.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -76,6 +78,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardStatusView;
@@ -93,7 +96,6 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -109,7 +111,7 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -165,7 +167,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
-import com.android.systemui.wallet.controller.QuickAccessWalletController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.After;
@@ -173,6 +174,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -257,11 +260,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private FragmentService mFragmentService;
@Mock private FragmentHostManager mFragmentHostManager;
- @Mock private QuickAccessWalletController mQuickAccessWalletController;
- @Mock private QRCodeScannerController mQrCodeScannerController;
@Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
@Mock private RecordingController mRecordingController;
- @Mock private ControlsComponent mControlsComponent;
@Mock private LockscreenGestureLogger mLockscreenGestureLogger;
@Mock private DumpManager mDumpManager;
@Mock private InteractionJankMonitor mInteractionJankMonitor;
@@ -282,6 +282,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+ @Mock private MotionEvent mDownMotionEvent;
+ @Captor
+ private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
+ mEmptySpaceClickListenerCaptor;
private NotificationPanelViewController.TouchHandler mTouchHandler;
private ConfigurationController mConfigurationController;
@@ -425,6 +429,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
+ when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
mMainHandler = new Handler(Looper.getMainLooper());
NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
@@ -512,6 +517,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.addCallback(mNotificationPanelViewController.mStatusBarStateListener);
mNotificationPanelViewController
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
+ verify(mNotificationStackScrollLayoutController)
+ .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
}
@After
@@ -750,6 +757,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);
@@ -1540,6 +1579,103 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
);
}
+ @Test
+ public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor).requestFaceAuth(true,
+ FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+ when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+ when(mUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(true, false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ }
+
+ @Test
+ public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(SHADE_LOCKED);
+
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+
+ }
+
+ /**
+ * When shade is flinging to close and this fling is not intercepted,
+ * {@link AmbientState#setIsClosing(boolean)} should be called before
+ * {@link NotificationStackScrollLayoutController#onExpansionStopped()}
+ * to ensure scrollY can be correctly set to be 0
+ */
+ @Test
+ public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
+ // Given: Shade is expanded
+ mNotificationPanelViewController.notifyExpandingFinished();
+ mNotificationPanelViewController.setIsClosing(false);
+
+ // When: Shade flings to close not canceled
+ mNotificationPanelViewController.notifyExpandingStarted();
+ mNotificationPanelViewController.setIsClosing(true);
+ mNotificationPanelViewController.onFlingEnd(false);
+
+ // Then: AmbientState's mIsClosing should be set to false
+ // before mNotificationStackScrollLayoutController.onExpansionStopped() is called
+ // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition
+ // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed
+ InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController);
+ inOrder.verify(mAmbientState).setIsClosing(false);
+ inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
+ }
+
private static MotionEvent createMotionEvent(int x, int y, int action) {
return MotionEvent.obtain(
/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index eb34561d15a0..cc45cf88fa18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -55,7 +56,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.animateAppearOnLockscreen()
clockView.measure(50, 50)
- verify(mockTextAnimator).glyphFilter = null
+ verify(mockTextAnimator).glyphFilter = any()
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -66,7 +67,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.measure(50, 50)
clockView.animateAppearOnLockscreen()
- verify(mockTextAnimator, times(2)).glyphFilter = null
+ verify(mockTextAnimator, times(2)).glyphFilter = any()
verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
verifyNoMoreInteractions(mockTextAnimator)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
index 5b34a95d4fb0..b761647e24e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
@@ -17,58 +17,58 @@ import org.mockito.MockitoAnnotations
@SmallTest
class UncaughtExceptionPreHandlerTest : SysuiTestCase() {
- private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
+ private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
- @Mock private lateinit var mockHandler: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler: UncaughtExceptionHandler
- @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager = UncaughtExceptionPreHandlerManager()
- }
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager = UncaughtExceptionPreHandlerManager()
+ }
- @Test
- fun registerHandler_registersOnceOnly() {
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ fun registerHandler_registersOnceOnly() {
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- fun registerHandler_setsUncaughtExceptionPreHandler() {
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager.registerHandler(mockHandler)
- assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
- }
+ @Test
+ fun registerHandler_setsUncaughtExceptionPreHandler() {
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager.registerHandler(mockHandler)
+ assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
+ }
- @Test
- fun registerHandler_preservesOriginalHandler() {
- Thread.setUncaughtExceptionPreHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ fun registerHandler_preservesOriginalHandler() {
+ Thread.setUncaughtExceptionPreHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- @Ignore
- fun registerHandler_toleratesHandlersThatThrow() {
- `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler2, only()).uncaughtException(any(), any())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ @Ignore
+ fun registerHandler_toleratesHandlersThatThrow() {
+ `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler2, only()).uncaughtException(any(), any())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- fun registerHandler_doesNotSetUpTwice() {
- UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
- assertThrows(IllegalStateException::class.java) {
- preHandlerManager.registerHandler(mockHandler)
+ @Test
+ fun registerHandler_doesNotSetUpTwice() {
+ UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
+ assertThrows(IllegalStateException::class.java) {
+ preHandlerManager.registerHandler(mockHandler)
+ }
}
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 8cb530c355bd..5fc0ffe42f55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -4,7 +4,7 @@ import android.testing.AndroidTestingRunner
import android.util.DisplayMetrics
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
import com.android.systemui.statusbar.phone.LockscreenGestureLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index f8a0d2fc415c..9c65fac1af45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ed8a3e16cdd1..4bed4a19b3d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -38,7 +38,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index a76676e01c15..d5f5105036d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,7 +43,7 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 82e32b2fdc64..09f8a10f88c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -34,10 +34,12 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@@ -135,6 +137,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
mListBuilder = new ShadeListBuilder(
mDumpManager,
@@ -1995,22 +1998,89 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ public void testActiveOrdering_withLegacyStability() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
+ }
+
+ @Test
+ public void testStableOrdering_withLegacyStability() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
+ mStabilityManager.setAllowEntryReordering(false);
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap
+ }
+
+ @Test
public void testStableOrdering() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
mStabilityManager.setAllowEntryReordering(false);
- assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG"); // X
- assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change
- assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X
- assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap
- verify(mStabilityManager, times(4)).onEntryReorderSuppressed();
+ // No input or output
+ assertOrder("", "", "", true);
+ // Remove everything
+ assertOrder("ABCDEFG", "", "", true);
+ // Literally no changes
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true);
+
+ // No stable order
+ assertOrder("", "ABCDEFG", "ABCDEFG", true);
+
+ // F moved after A, and...
+ assertOrder("ABCDEFG", "AFBCDEG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false); // Insert X before F
+ assertOrder("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false); // Insert X after F
+ assertOrder("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false); // Insert X where F was
+
+ // B moved after F, and...
+ assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false); // Insert X before B
+ assertOrder("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false); // Insert X after B
+ assertOrder("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false); // Insert X where B was
+
+ // Swap F and B, and...
+ assertOrder("ABCDEFG", "AFCDEBG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false); // Insert X before F
+ assertOrder("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false); // Insert X after F
+ assertOrder("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false); // Insert X between CD (or: ABCXDEFG)
+ assertOrder("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false); // Insert X between DE (or: ABCDEFXG)
+ assertOrder("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false); // Insert X before B
+ assertOrder("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false); // Insert X after B
+
+ // Remove a bunch of entries at once
+ assertOrder("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true);
+
+ // Remove a bunch of entries and scramble
+ assertOrder("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false);
+
+ // Add a bunch of entries at once
+ assertOrder("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true);
+
+ // Add a bunch of entries and reverse originals
+ // NOTE: Some of these don't have obviously correct answers
+ assertOrder("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false); // appended
+ assertOrder("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false); // prepended
+ assertOrder("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false); // closer to back: append
+ assertOrder("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false); // closer to front: prepend
+ assertOrder("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false); // split new entries
+
+ // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+ assertOrder("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false);
}
@Test
public void testActiveOrdering() {
- assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG"); // X
- assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change
- assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X
- assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+ assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
}
@Test
@@ -2062,6 +2132,52 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ public void stableOrderingDisregardedWithSectionChange() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+ // GIVEN the first sectioner's packages can be changed from run-to-run
+ List<String> mutableSectionerPackages = new ArrayList<>();
+ mutableSectionerPackages.add(PACKAGE_1);
+ mListBuilder.setSectioners(asList(
+ new PackageSectioner(mutableSectionerPackages, null),
+ new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(4);
+ addNotif(1, PACKAGE_1).setRank(5);
+ addNotif(2, PACKAGE_2).setRank(1);
+ addNotif(3, PACKAGE_2).setRank(2);
+ addNotif(4, PACKAGE_3).setRank(3);
+ dispatchBuild();
+
+ // VERIFY the order and that entry reordering has not been suppressed
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3),
+ notif(4)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the first section now claims PACKAGE_3 notifications
+ mutableSectionerPackages.add(PACKAGE_3);
+ dispatchBuild();
+
+ // VERIFY the re-sectioned notification is inserted at #1 of the first section, which
+ // is the correct position based on its rank, rather than #3 in the new section simply
+ // because it was #3 in its previous section.
+ verifyBuiltList(
+ notif(4),
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+ }
+
+ @Test
public void testStableChildOrdering() {
// WHEN the list is originally built with reordering disabled
mStabilityManager.setAllowEntryReordering(false);
@@ -2112,6 +2228,85 @@ public class ShadeListBuilderTest extends SysuiTestCase {
);
}
+ @Test
+ public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() {
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false);
+
+ // GIVEN a notification group is on screen
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(2);
+ addNotif(1, PACKAGE_1).setRank(3);
+ addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+ addGroupChild(3, PACKAGE_1, "group").setRank(5);
+ addGroupChild(4, PACKAGE_1, "group").setRank(6);
+ dispatchBuild();
+
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ group(
+ summary(2),
+ child(3),
+ child(4)
+ )
+ );
+
+ // WHEN the notification summary rank increases and children removed
+ setNewRank(notif(2).entry, 1);
+ mEntrySet.remove(4);
+ mEntrySet.remove(3);
+ dispatchBuild();
+
+ // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked,
+ // despite visual stability being active
+ verifyBuiltList(
+ notif(2),
+ notif(0),
+ notif(1)
+ );
+ }
+
+ @Test
+ public void groupRevertingToSummaryRetainsStablePosition() {
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
+
+ // GIVEN a notification group is on screen
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(2);
+ addNotif(1, PACKAGE_1).setRank(3);
+ addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+ addGroupChild(3, PACKAGE_1, "group").setRank(5);
+ addGroupChild(4, PACKAGE_1, "group").setRank(6);
+ dispatchBuild();
+
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ group(
+ summary(2),
+ child(3),
+ child(4)
+ )
+ );
+
+ // WHEN the notification summary rank increases and children removed
+ setNewRank(notif(2).entry, 1);
+ mEntrySet.remove(4);
+ mEntrySet.remove(3);
+ dispatchBuild();
+
+ // VERIFY the summary stays in the same location on rebuild
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2)
+ );
+ }
+
private static void setNewRank(NotificationEntry entry, int rank) {
entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());
}
@@ -2255,26 +2450,35 @@ public class ShadeListBuilderTest extends SysuiTestCase {
return addGroupChildWithTag(index, packageId, groupId, null);
}
- private void assertOrder(String visible, String active, String expected) {
+ private void assertOrder(String visible, String active, String expected,
+ boolean isOrderedCorrectly) {
StringBuilder differenceSb = new StringBuilder();
+ NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
for (char c : active.toCharArray()) {
if (visible.indexOf(c) < 0) differenceSb.append(c);
}
String difference = differenceSb.toString();
+ int globalIndex = 0;
for (int i = 0; i < visible.length(); i++) {
- addNotif(i, String.valueOf(visible.charAt(i)))
- .setRank(active.indexOf(visible.charAt(i)))
+ final char c = visible.charAt(i);
+ // Skip notifications which aren't active anymore
+ if (!active.contains(String.valueOf(c))) continue;
+ addNotif(globalIndex++, String.valueOf(c))
+ .setRank(active.indexOf(c))
+ .setSection(section)
.setStableIndex(i);
-
}
- for (int i = 0; i < difference.length(); i++) {
- addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
- .setRank(active.indexOf(difference.charAt(i)))
+ for (char c : difference.toCharArray()) {
+ addNotif(globalIndex++, String.valueOf(c))
+ .setRank(active.indexOf(c))
+ .setSection(section)
.setStableIndex(-1);
}
+ clearInvocations(mStabilityManager);
+
dispatchBuild();
StringBuilder resultSb = new StringBuilder();
for (int i = 0; i < expected.length(); i++) {
@@ -2284,6 +2488,9 @@ public class ShadeListBuilderTest extends SysuiTestCase {
assertEquals("visible [" + visible + "] active [" + active + "]",
expected, resultSb.toString());
mEntrySet.clear();
+
+ verify(mStabilityManager, isOrderedCorrectly ? never() : times(1))
+ .onEntryReorderSuppressed();
}
private int nextId(String packageName) {
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 dcf245525f10..f4adf6927e31 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.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(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.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(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.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(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.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
@@ -254,36 +254,28 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
// GIVEN an inflated, minimized notification
setSectionIsLowPriority(true);
- mCollectionListener.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertTrue(mParamsCaptor.getValue().isLowPriority());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
// WHEN notification is moved under a parent
- NotificationEntry groupSummary = getNotificationEntryBuilder()
- .setParent(ROOT_ENTRY)
- .setGroupSummary(mContext, true)
- .setGroup(mContext, TEST_GROUP_KEY)
- .build();
- GroupEntry parent = mock(GroupEntry.class);
- when(parent.getSummary()).thenReturn(groupSummary);
- NotificationEntryBuilder.setNewParent(mEntry, parent);
- mCollectionListener.onEntryInit(groupSummary);
- mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry, groupSummary));
+ NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class));
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
// THEN we rebind it as not-minimized
verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
- // THEN we filter it because the parent summary is not yet inflated.
- assertTrue(mUninflatedFilter.shouldFilterOut(mEntry, 0));
+ // THEN we do not filter it because it's not the first inflation.
+ assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void testEntryRankChangeWillNotRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -302,7 +294,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testDoesntFilterInflatedNotifs() {
// GIVEN an inflated notification
- mCollectionListener.onEntryInit(mEntry);
+ mCollectionListener.onEntryAdded(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -338,9 +330,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mCollectionListener.onEntryInit(entry);
}
- mCollectionListener.onEntryInit(summary);
+ mCollectionListener.onEntryAdded(summary);
for (NotificationEntry entry : children) {
- mCollectionListener.onEntryInit(entry);
+ mCollectionListener.onEntryAdded(entry);
}
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry));
@@ -401,40 +393,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
}
@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()
@@ -454,7 +412,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mNotifInflater.invokeInflateCallbackForEntry(child1);
mNotifInflater.invokeInflateCallbackForEntry(summary);
- // THEN the entire group is no longer filtered out
+ // THEN the entire group is still filtered out
assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
@@ -536,11 +494,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private void fireAddEvents(NotificationEntry entry) {
mCollectionListener.onEntryInit(entry);
- mCollectionListener.onEntryInit(entry);
- }
-
- private void fireUpdateEvents(NotificationEntry entry) {
- mCollectionListener.onEntryUpdated(entry);
+ mCollectionListener.onEntryAdded(entry);
}
private static final String TEST_MESSAGE = "TEST_MESSAGE";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
new file mode 100644
index 000000000000..1cdd023dd01c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.Log
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class SemiStableSortTest : SysuiTestCase() {
+
+ var shuffleInput: Boolean = false
+ var testStabilizeTo: Boolean = false
+ var sorter: SemiStableSort? = null
+
+ @Before
+ fun setUp() {
+ shuffleInput = false
+ sorter = null
+ }
+
+ private fun stringStabilizeTo(
+ stableOrder: String,
+ activeOrder: String,
+ ): Pair<String, Boolean> {
+ val actives = activeOrder.toMutableList()
+ val result = mutableListOf<Char>()
+ return (sorter ?: SemiStableSort())
+ .stabilizeTo(
+ actives,
+ { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+ result,
+ )
+ .let { ordered -> result.joinToString("") to ordered }
+ }
+
+ private fun stringSort(
+ stableOrder: String,
+ activeOrder: String,
+ ): Pair<String, Boolean> {
+ val actives = activeOrder.toMutableList()
+ if (shuffleInput) {
+ actives.shuffle()
+ }
+ return (sorter ?: SemiStableSort())
+ .sort(
+ actives,
+ { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+ compareBy { activeOrder.indexOf(it) },
+ )
+ .let { ordered -> actives.joinToString("") to ordered }
+ }
+
+ private fun testCase(
+ stableOrder: String,
+ activeOrder: String,
+ expected: String,
+ expectOrdered: Boolean,
+ ) {
+ val (mergeResult, ordered) =
+ if (testStabilizeTo) stringStabilizeTo(stableOrder, activeOrder)
+ else stringSort(stableOrder, activeOrder)
+ val resultPass = expected == mergeResult
+ val orderedPass = ordered == expectOrdered
+ val pass = resultPass && orderedPass
+ val resultSuffix =
+ if (resultPass) "result=$expected" else "expected=$expected got=$mergeResult"
+ val orderedSuffix =
+ if (orderedPass) "ordered=$ordered" else "expected ordered to be $expectOrdered"
+ val readableResult = "stable=$stableOrder active=$activeOrder $resultSuffix $orderedSuffix"
+ Log.d("SemiStableSortTest", "${if (pass) "PASS" else "FAIL"}: $readableResult")
+ if (!pass) {
+ throw AssertionError("Test case failed: $readableResult")
+ }
+ }
+
+ private fun runAllTestCases() {
+ // No input or output
+ testCase("", "", "", true)
+ // Remove everything
+ testCase("ABCDEFG", "", "", true)
+ // Literally no changes
+ testCase("ABCDEFG", "ABCDEFG", "ABCDEFG", true)
+
+ // No stable order
+ testCase("", "ABCDEFG", "ABCDEFG", true)
+
+ // F moved after A, and...
+ testCase("ABCDEFG", "AFBCDEG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false) // Insert X before F
+ testCase("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false) // Insert X after F
+ testCase("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false) // Insert X where F was
+
+ // B moved after F, and...
+ testCase("ABCDEFG", "ACDEFBG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false) // Insert X before B
+ testCase("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false) // Insert X after B
+ testCase("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false) // Insert X where B was
+
+ // Swap F and B, and...
+ testCase("ABCDEFG", "AFCDEBG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false) // Insert X before F
+ testCase("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false) // Insert X after F
+ testCase("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false) // Insert X between CD (Alt: ABCXDEFG)
+ testCase("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false) // Insert X between DE (Alt: ABCDEFXG)
+ testCase("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false) // Insert X before B
+ testCase("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false) // Insert X after B
+
+ // Remove a bunch of entries at once
+ testCase("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true)
+
+ // Remove a bunch of entries and scramble
+ testCase("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false)
+
+ // Add a bunch of entries at once
+ testCase("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true)
+
+ // Add a bunch of entries and reverse originals
+ // NOTE: Some of these don't have obviously correct answers
+ testCase("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false) // appended
+ testCase("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false) // prepended
+ testCase("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false) // closer to back: append
+ testCase("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false) // closer to front: prepend
+ testCase("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false) // split new entries
+
+ // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+ testCase("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false)
+ }
+
+ @Test
+ fun testSort() {
+ testStabilizeTo = false
+ shuffleInput = false
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testSortWithSingleInstance() {
+ testStabilizeTo = false
+ shuffleInput = false
+ sorter = SemiStableSort()
+ runAllTestCases()
+ }
+
+ @Test
+ fun testSortWithShuffledInput() {
+ testStabilizeTo = false
+ shuffleInput = true
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testStabilizeTo() {
+ testStabilizeTo = true
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testStabilizeToWithSingleInstance() {
+ testStabilizeTo = true
+ sorter = SemiStableSort()
+ runAllTestCases()
+ }
+
+ @Test
+ fun testIsSorted() {
+ val intCmp = Comparator<Int> { x, y -> Integer.compare(x, y) }
+ SemiStableSort.apply {
+ assertTrue(emptyList<Int>().isSorted(intCmp))
+ assertTrue(listOf(1).isSorted(intCmp))
+ assertTrue(listOf(1, 2).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3, 4).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3, 4, 5).isSorted(intCmp))
+ assertTrue(listOf(1, 1, 1, 1, 1).isSorted(intCmp))
+ assertTrue(listOf(1, 1, 2, 2, 3, 3).isSorted(intCmp))
+ assertFalse(listOf(2, 1).isSorted(intCmp))
+ assertFalse(listOf(2, 1, 2).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 1).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 3, 2, 5).isSorted(intCmp))
+ assertFalse(listOf(5, 2, 3, 4, 5).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 3, 4, 1).isSorted(intCmp))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
new file mode 100644
index 000000000000..20369546d68a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeListBuilderHelperTest : SysuiTestCase() {
+
+ @Test
+ fun testGetContiguousSubLists() {
+ assertThat(getContiguousSubLists("AAAAAA".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A', 'A', 'A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBB".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABAA".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B'),
+ listOf('A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABAA".toList(), minLength = 2) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B', 'B'),
+ listOf('C', 'C'),
+ listOf('D'),
+ listOf('E', 'E', 'E'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList(), minLength = 2) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B', 'B'),
+ listOf('C', 'C'),
+ listOf('E', 'E', 'E'),
+ )
+ .inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 11798a7a4f96..87f4c323b7cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -361,6 +361,22 @@ class AmbientStateTest : SysuiTestCase() {
assertThat(sut.isOnKeyguard).isFalse()
}
// endregion
+
+ // region mIsClosing
+ @Test
+ fun isClosing_whenShadeClosing_shouldReturnTrue() {
+ sut.setIsClosing(true)
+
+ assertThat(sut.isClosing).isTrue()
+ }
+
+ @Test
+ fun isClosing_whenShadeFinishClosing_shouldReturnFalse() {
+ sut.setIsClosing(false)
+
+ assertThat(sut.isClosing).isFalse()
+ }
+ // endregion
}
// region Arrange helper methods.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 43530365360b..35c8b61b6383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -728,6 +728,57 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
+ @Test
+ public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
+ // Given: shade is not closing, scrollY is 0
+ mAmbientState.setScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ mAmbientState.setIsClosing(false);
+
+ // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+ mStackScroller.setOwnScrollY(1);
+
+ // Then: scrollY should be set to 1
+ assertEquals(1, mAmbientState.getScrollY());
+
+ // Reset scrollY back to 0 to avoid interfering with other tests
+ mStackScroller.setOwnScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ }
+
+ @Test
+ public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() {
+ // Given: shade is closing, scrollY is 0
+ mAmbientState.setScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ mAmbientState.setIsClosing(true);
+
+ // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+ mStackScroller.setOwnScrollY(1);
+
+ // Then: scrollY should not change, it should still be 0
+ assertEquals(0, mAmbientState.getScrollY());
+
+ // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests
+ mAmbientState.setIsClosing(false);
+ mStackScroller.setOwnScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ }
+
+ @Test
+ public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() {
+ // Given: mAmbientState.mIsClosing is set to be true
+ // mIsExpanded is set to be false
+ mAmbientState.setIsClosing(true);
+ mStackScroller.setIsExpanded(false);
+
+ // When: onExpansionStopped is called
+ mStackScroller.onExpansionStopped();
+
+ // Then: mAmbientState.scrollY should be set to be 0
+ assertEquals(mAmbientState.getScrollY(), 0);
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
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..d4cd4a608340 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
@@ -98,7 +98,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 +272,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 +296,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
@@ -1017,6 +1018,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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 65e2964ea332..3a0a94ddd511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3a006adb1933..36e76f47ca4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -49,9 +49,9 @@ import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeExpansionStateManager;
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/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 0e75c74ef6f5..b32058fca109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.google.common.truth.Truth.assertThat
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..e56623f9fea2 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,7 +22,7 @@ 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
@@ -125,19 +125,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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index c9f2b4db81ef..13e9f608158e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.temporarydisplay
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
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..fa78b3832158 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,10 +16,6 @@
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.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -31,19 +27,18 @@ 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.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 +48,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 +58,277 @@ 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
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,
+ )
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)
- }
+ fun displayView_loadedIcon_correctlyRendered() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
- @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)
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = null,
+ )
)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
- @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)
- )
- 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 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)
+ 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.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
- @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)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+ val iconView = getChipbarView().getStartIconView()
+ assertThat(iconView.contentDescription)
+ .isEqualTo(contentDescription.loadContentDescription(context))
}
@Test
- fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
- underTest.displayView(transferToReceiverSucceeded(undoCallback = null))
+ fun displayView_loadedText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("display view text here"),
+ endItem = null,
+ )
+ )
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(getChipbarView().getChipText()).isEqualTo("display view text here")
}
@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_resourceText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Resource(R.string.screenrecord_start_error),
+ 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(context.getString(R.string.screenrecord_start_error))
}
@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_endItemNull_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
- assertThat(undoCallbackCalled).isFalse()
+ 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_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_endItemLoading_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
- assertThat(undoCallbackCalled).isTrue()
+ 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 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_endItemError_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
)
- }
- @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.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
@Test
- fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback = null))
+ 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 = {},
+ ),
+ )
+ )
- 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.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().text).isEqualTo("button text")
+ assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue()
}
@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()
- }
-
- @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()
- }
+ fun displayView_endItemButtonClicked_falseTap_listenerNotRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
- @Test
- fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- underTest.displayView(transferToThisDeviceSucceeded(undoCallback))
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
+ )
- getChipView().getUndoButton().performClick()
+ getChipbarView().getEndButton().performClick()
- 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
- )
+ assertThat(isClicked).isFalse()
}
@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)
- }
+ fun displayView_endItemButtonClicked_notFalseTap_listenerRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
- @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)
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
)
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
- @Test
- fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
- underTest.displayView(almostCloseToStartCast())
- underTest.displayView(transferToReceiverTriggered())
+ getChipbarView().getEndButton().performClick()
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+ assertThat(isClicked).isTrue()
}
@Test
- fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
- underTest.displayView(transferToReceiverTriggered())
- underTest.displayView(transferToReceiverSucceeded())
-
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
- }
+ fun updateView_viewUpdated() {
+ // First, display a view
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
- underTest.displayView(transferToReceiverTriggered())
underTest.displayView(
- transferToReceiverSucceeded(
- object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
)
)
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
- underTest.displayView(transferToReceiverSucceeded())
- underTest.displayView(almostCloseToStartCast())
-
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @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..8f32e0fd1de5 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,7 +24,6 @@ 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.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -39,7 +38,6 @@ class FakeChipbarCoordinator(
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- uiEventLogger: MediaTttSenderUiEventLogger,
falsingManager: FalsingManager,
falsingCollector: FalsingCollector,
viewUtil: ViewUtil,
@@ -52,7 +50,6 @@ class FakeChipbarCoordinator(
accessibilityManager,
configurationController,
powerManager,
- uiEventLogger,
falsingManager,
falsingCollector,
viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
index d4b41c18e123..a363a037c499 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -97,6 +97,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
createUserRecord(2),
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
)
)
var models: List<UserModel>? = null
@@ -176,15 +177,17 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
createUserRecord(2),
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
)
)
var models: List<UserActionModel>? = null
val job = underTest.actions.onEach { models = it }.launchIn(this)
- assertThat(models).hasSize(3)
+ assertThat(models).hasSize(4)
assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+ assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
job.cancel()
}
@@ -200,6 +203,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
isAddUser = action == UserActionModel.ADD_USER,
isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+ isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
index c3a9705bf6ba..6a17c8ddc63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -64,13 +64,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - not actionable when locked and not locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(false)
keyguardRepository.setKeyguardShowing(false)
@@ -92,13 +86,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - actionable when locked and not locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(true)
keyguardRepository.setKeyguardShowing(false)
@@ -120,13 +108,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - actionable when locked and locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(true)
keyguardRepository.setKeyguardShowing(true)
@@ -182,6 +164,10 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
verify(activityStarter).startActivity(any(), anyBoolean())
}
+ private fun setActions() {
+ userRepository.setActions(UserActionModel.values().toList())
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 0344e3f991e2..c12a868dbaed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -268,6 +268,26 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
@Test
+ fun `menu actions`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(UserActionModel.values().toList())
+ var actions: List<UserActionViewModel>? = null
+ val job = underTest.menu.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions?.map { it.viewKey })
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun `isFinishRequested - finishes when user is switched`() =
runBlocking(IMMEDIATE) {
setUsers(count = 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
deleted file mode 100644
index 5e09b81da4e8..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.util.collection
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class RingBufferTest : SysuiTestCase() {
-
- private val buffer = RingBuffer(5) { TestElement() }
-
- private val history = mutableListOf<TestElement>()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun testBarelyFillBuffer() {
- fillBuffer(5)
-
- assertEquals(0, buffer[0].id)
- assertEquals(1, buffer[1].id)
- assertEquals(2, buffer[2].id)
- assertEquals(3, buffer[3].id)
- assertEquals(4, buffer[4].id)
- }
-
- @Test
- fun testPartiallyFillBuffer() {
- fillBuffer(3)
-
- assertEquals(3, buffer.size)
-
- assertEquals(0, buffer[0].id)
- assertEquals(1, buffer[1].id)
- assertEquals(2, buffer[2].id)
-
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] }
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] }
- }
-
- @Test
- fun testSpinBuffer() {
- fillBuffer(277)
-
- assertEquals(272, buffer[0].id)
- assertEquals(273, buffer[1].id)
- assertEquals(274, buffer[2].id)
- assertEquals(275, buffer[3].id)
- assertEquals(276, buffer[4].id)
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] }
-
- assertEquals(5, buffer.size)
- }
-
- @Test
- fun testElementsAreRecycled() {
- fillBuffer(23)
-
- assertSame(history[4], buffer[1])
- assertSame(history[9], buffer[1])
- assertSame(history[14], buffer[1])
- assertSame(history[19], buffer[1])
- }
-
- @Test
- fun testIterator() {
- fillBuffer(13)
-
- val iterator = buffer.iterator()
-
- for (i in 0 until 5) {
- assertEquals(history[8 + i], iterator.next())
- }
- assertFalse(iterator.hasNext())
- assertThrows(NoSuchElementException::class.java) { iterator.next() }
- }
-
- @Test
- fun testForEach() {
- fillBuffer(13)
- var i = 8
-
- buffer.forEach {
- assertEquals(history[i], it)
- i++
- }
- assertEquals(13, i)
- }
-
- private fun fillBuffer(count: Int) {
- for (i in 0 until count) {
- val elem = buffer.advance()
- elem.id = history.size
- history.add(elem)
- }
- }
-}
-
-private class TestElement(var id: Int = 0) {
- override fun toString(): String {
- return "{TestElement $id}"
- }
-} \ No newline at end of file
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/proto/src/system_messages.proto b/proto/src/system_messages.proto
index a94bfe281be1..12e722601e25 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -61,7 +61,7 @@ message SystemMessage {
// Notify the user that they should select an input method
// Package: android
- NOTE_SELECT_INPUT_METHOD = 8;
+ NOTE_SELECT_INPUT_METHOD = 8 [deprecated = true];
// Notify the user about limited functionality before decryption
// Package: android
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index ec30369bd099..02053cc7cfd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -31,6 +31,7 @@ import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IInputConstants;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
@@ -75,7 +76,7 @@ class InputController {
@interface PhysType {
}
- private final Object mLock;
+ final Object mLock;
/* Token -> file descriptor associations. */
@VisibleForTesting
@@ -220,6 +221,19 @@ class InputController {
}
}
+ /**
+ * @return the device id for a given token (identifiying a device)
+ */
+ int getInputDeviceId(IBinder token) {
+ synchronized (mLock) {
+ final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(token);
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("Could not get device id for given token");
+ }
+ return inputDeviceDescriptor.getInputDeviceId();
+ }
+ }
+
void setShowPointerIcon(boolean visible, int displayId) {
mInputManagerInternal.setPointerIconVisible(visible, displayId);
}
@@ -393,10 +407,22 @@ class InputController {
+ inputDeviceDescriptor.getCreationOrderNumber());
fout.println(" type: " + inputDeviceDescriptor.getType());
fout.println(" phys: " + inputDeviceDescriptor.getPhys());
+ fout.println(
+ " inputDeviceId: " + inputDeviceDescriptor.getInputDeviceId());
}
}
}
+ @VisibleForTesting
+ void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId,
+ String phys, int inputDeviceId) {
+ synchronized (mLock) {
+ mInputDeviceDescriptors.put(deviceToken,
+ new InputDeviceDescriptor(fd, () -> {}, type, displayId, phys,
+ inputDeviceId));
+ }
+ }
+
private static native int nativeOpenUinputDpad(String deviceName, int vendorId,
int productId, String phys);
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
@@ -493,16 +519,20 @@ class InputController {
private final @Type int mType;
private final int mDisplayId;
private final String mPhys;
+ // The input device id that was associated to the device by the InputReader on device
+ // creation.
+ private final int mInputDeviceId;
// Monotonically increasing number; devices with lower numbers were created earlier.
private final long mCreationOrderNumber;
InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
- int displayId, String phys) {
+ int displayId, String phys, int inputDeviceId) {
mFd = fd;
mDeathRecipient = deathRecipient;
mType = type;
mDisplayId = displayId;
mPhys = phys;
+ mInputDeviceId = inputDeviceId;
mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
}
@@ -533,6 +563,10 @@ class InputController {
public String getPhys() {
return mPhys;
}
+
+ public int getInputDeviceId() {
+ return mInputDeviceId;
+ }
}
private final class BinderDeathRecipient implements IBinder.DeathRecipient {
@@ -558,6 +592,8 @@ class InputController {
private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
private final InputManager.InputDeviceListener mListener;
+ private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
+
WaitForDevice(String deviceName, int vendorId, int productId) {
mListener = new InputManager.InputDeviceListener() {
@Override
@@ -572,6 +608,7 @@ class InputController {
if (id.getVendorId() != vendorId || id.getProductId() != productId) {
return;
}
+ mInputDeviceId = deviceId;
mDeviceAddedLatch.countDown();
}
@@ -588,8 +625,13 @@ class InputController {
InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
}
- /** Note: This must not be called from {@link #mHandler}'s thread. */
- void waitForDeviceCreation() throws DeviceCreationException {
+ /**
+ * Note: This must not be called from {@link #mHandler}'s thread.
+ * @throws DeviceCreationException if the device was not created successfully within the
+ * timeout.
+ * @return The id of the created input device.
+ */
+ int waitForDeviceCreation() throws DeviceCreationException {
try {
if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
throw new DeviceCreationException(
@@ -599,6 +641,12 @@ class InputController {
throw new DeviceCreationException(
"Interrupted while waiting for virtual device to be created.", e);
}
+ if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) {
+ throw new IllegalStateException(
+ "Virtual input device was created with an invalid "
+ + "id=" + mInputDeviceId);
+ }
+ return mInputDeviceId;
}
@Override
@@ -643,6 +691,8 @@ class InputController {
final int fd;
final BinderDeathRecipient binderDeathRecipient;
+ final int inputDeviceId;
+
setUniqueIdAssociation(displayId, phys);
try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
fd = deviceOpener.get();
@@ -652,7 +702,7 @@ class InputController {
}
// The fd is valid from here, so ensure that all failures close the fd after this point.
try {
- waiter.waitForDeviceCreation();
+ inputDeviceId = waiter.waitForDeviceCreation();
binderDeathRecipient = new BinderDeathRecipient(deviceToken);
try {
@@ -672,7 +722,8 @@ class InputController {
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
+ new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys,
+ inputDeviceId));
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 2835b69b3039..5ebbf07526f1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -498,6 +498,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
+ public int getInputDeviceId(IBinder token) {
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mInputController.getInputDeviceId(token);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+
+ @Override // Binder call
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
final long binderToken = Binder.clearCallingIdentity();
try {
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/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 202f47759272..5d46de335781 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -152,6 +152,8 @@ final class UiModeManagerService extends SystemService {
// flag set by resource, whether to start dream immediately upon docking even if unlocked.
private boolean mStartDreamImmediatelyOnDock = true;
+ // flag set by resource, whether to disable dreams when ambient mode suppression is enabled.
+ private boolean mDreamsDisabledByAmbientModeSuppression = false;
// flag set by resource, whether to enable Car dock launch when starting car mode.
private boolean mEnableCarDockLaunch = true;
// flag set by resource, whether to lock UI mode to the default one or not.
@@ -364,6 +366,11 @@ final class UiModeManagerService extends SystemService {
mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;
}
+ @VisibleForTesting
+ void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) {
+ mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression;
+ }
+
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
@@ -424,6 +431,8 @@ final class UiModeManagerService extends SystemService {
final Resources res = context.getResources();
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
+ mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
mNightMode = res.getInteger(
com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
@@ -1827,10 +1836,14 @@ final class UiModeManagerService extends SystemService {
// Send the new configuration.
applyConfigurationExternallyLocked();
+ final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression
+ && mLocalPowerManager.isAmbientDisplaySuppressed();
+
// If we did not start a dock app, then start dreaming if appropriate.
- if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
- || mWindowManager.isKeyguardShowingAndNotOccluded()
- || !mPowerManager.isInteractive())) {
+ if (category != null && !dockAppStarted && !dreamsSuppressed && (
+ mStartDreamImmediatelyOnDock
+ || mWindowManager.isKeyguardShowingAndNotOccluded()
+ || !mPowerManager.isInteractive())) {
mInjector.startDreamWhenDockedIfAppropriate(getContext());
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7a09109e377b..63f81822e867 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13373,27 +13373,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 +14692,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 1e1ebeba5c23..db3ef3d51b16 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -44,6 +44,7 @@ import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
@@ -440,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;
@@ -477,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)) {
@@ -534,6 +535,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}, mBroadcastConsumerSkipAndCanceled, true);
}
+ final int policy = (r.options != null)
+ ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+ if (policy == BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) {
+ forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+ // We only allow caller to remove broadcasts they enqueued
+ return (r.callingUid == testRecord.callingUid)
+ && (r.userId == testRecord.userId)
+ && r.matchesDeliveryGroup(testRecord);
+ }, mBroadcastConsumerSkipAndCanceled, true);
+ }
+
if (r.isReplacePending()) {
// Leave the skipped broadcasts intact in queue, so that we can
// replace them at their current position during enqueue below
@@ -804,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);
+ }
}
}
@@ -913,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);
}
@@ -1205,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 2d825955ed6c..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;
@@ -796,6 +815,16 @@ final class BroadcastRecord extends Binder {
}
}
+ public boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
+ final String key = (options != null) ? options.getDeliveryGroupKey() : null;
+ final String otherKey = (other.options != null)
+ ? other.options.getDeliveryGroupKey() : null;
+ if (key == null && otherKey == null) {
+ return intent.filterEquals(other.intent);
+ }
+ return Objects.equals(key, otherKey);
+ }
+
@Override
public String toString() {
if (mCachedToString == null) {
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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 216a48ec699c..3fa41c0f0420 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
@@ -1482,7 +1481,7 @@ class UserController implements Handler.Callback {
// defined
boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {
checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
- MANAGE_USERS, CREATE_USERS);
+ MANAGE_USERS, INTERACT_ACROSS_USERS);
// DEFAULT_DISPLAY is used for the current foreground user only
Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
index dec1b559556a..5bc9d2341535 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
@@ -54,7 +54,7 @@ public class AuthSessionCoordinator implements AuthSessionListener {
private AuthResultCoordinator mAuthResultCoordinator;
public AuthSessionCoordinator() {
- this(SystemClock.currentNetworkTimeClock());
+ this(SystemClock.elapsedRealtimeClock());
}
@VisibleForTesting
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/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
index d9bd04d3f1c8..6605d49ece9b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
@@ -22,7 +22,6 @@ import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMET
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import android.hardware.biometrics.BiometricManager;
-import android.os.SystemClock;
import android.util.Slog;
import java.time.Clock;
@@ -43,10 +42,6 @@ class MultiBiometricLockoutState {
private final Map<Integer, Map<Integer, AuthenticatorState>> mCanUserAuthenticate;
private final Clock mClock;
- MultiBiometricLockoutState() {
- this(SystemClock.currentNetworkTimeClock());
- }
-
MultiBiometricLockoutState(Clock clock) {
mCanUserAuthenticate = new HashMap<>();
mClock = clock;
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/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f599acac7ed4..2e5663db57b5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import android.annotation.NonNull;
@@ -59,6 +60,7 @@ import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.function.Supplier;
@@ -92,7 +94,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
private long mWaitForAuthKeyguard;
private long mWaitForAuthBp;
private long mIgnoreAuthFor;
+ private long mSideFpsLastAcquireStartTime;
private Runnable mAuthSuccessRunnable;
+ private final Clock mClock;
FingerprintAuthenticationClient(
@NonNull Context context,
@@ -117,7 +121,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
- @Authenticators.Types int biometricStrength) {
+ @Authenticators.Types int biometricStrength,
+ @NonNull Clock clock) {
super(
context,
lazyDaemon,
@@ -161,6 +166,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
mBiometricStrength = biometricStrength;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+ mSideFpsLastAcquireStartTime = -1;
+ mClock = clock;
if (mSensorProps.isAnySidefpsType()) {
if (Build.isDebuggable()) {
@@ -246,8 +253,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
return;
}
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
- Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power for: "
- + delay + "ms");
+
+ if (mSideFpsLastAcquireStartTime != -1) {
+ delay = Math.max(0,
+ delay - (mClock.millis() - mSideFpsLastAcquireStartTime));
+ }
+
+ Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps "
+ + "waiting for power until: " + delay + "ms");
}
if (mHandler.hasMessages(MESSAGE_FINGER_UP)) {
@@ -271,13 +284,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
super.onAcquired(acquiredInfo, vendorCode);
if (mSensorProps.isAnySidefpsType()) {
+ if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
+ mSideFpsLastAcquireStartTime = mClock.millis();
+ }
final boolean shouldLookForVendor =
mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;
final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;
final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;
final boolean ignorePowerPress =
- (acquireMessageMatch && !shouldLookForVendor) || (shouldLookForVendor
- && acquireMessageMatch && vendorMessageMatch);
+ acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);
if (ignorePowerPress) {
Slog.d(TAG, "(sideFPS) onFingerUp");
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 774aff1dd72c..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
@@ -47,6 +47,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseArray;
@@ -58,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;
@@ -449,7 +451,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties(), mHandler,
- Utils.getCurrentStrength(sensorId));
+ Utils.getCurrentStrength(sensorId),
+ SystemClock.elapsedRealtimeClock());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -777,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/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 0770062cd4d3..6a010424db13 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -29,6 +29,8 @@ import android.os.ServiceManager;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;
import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
@@ -47,7 +49,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
private static final List<String> SERVICE_NAMES = Arrays.asList(
IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab");
- private final com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl mHalAidl;
+ private final BroadcastRadioServiceImpl mHalAidl;
private final BroadcastRadioService mService;
/**
@@ -65,10 +67,15 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
}
IRadioServiceAidlImpl(BroadcastRadioService service, ArrayList<String> serviceList) {
+ this(service, new BroadcastRadioServiceImpl(serviceList));
Slogf.i(TAG, "Initialize BroadcastRadioServiceAidl(%s)", service);
- mService = Objects.requireNonNull(service);
- mHalAidl =
- new com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl(serviceList);
+ }
+
+ @VisibleForTesting
+ IRadioServiceAidlImpl(BroadcastRadioService service, BroadcastRadioServiceImpl halAidl) {
+ mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
+ mHalAidl = Objects.requireNonNull(halAidl,
+ "Broadcast radio service implementation for AIDL HAL cannot be null");
}
@Override
@@ -96,8 +103,8 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
if (isDebugEnabled()) {
Slogf.d(TAG, "Adding announcement listener for %s", Arrays.toString(enabledTypes));
}
- Objects.requireNonNull(enabledTypes);
- Objects.requireNonNull(listener);
+ Objects.requireNonNull(enabledTypes, "Enabled announcement types cannot be null");
+ Objects.requireNonNull(listener, "Announcement listener cannot be null");
mService.enforcePolicyAccess();
return mHalAidl.addAnnouncementListener(enabledTypes, listener);
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 28b6d02581be..a8e4034e3f86 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.broadcastradio.hal2.AnnouncementAggregator;
import java.io.FileDescriptor;
@@ -53,7 +54,7 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
private final List<RadioManager.ModuleProperties> mV1Modules;
IRadioServiceHidlImpl(BroadcastRadioService service) {
- mService = Objects.requireNonNull(service);
+ mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");
mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);
mV1Modules = mHal1.loadModules();
OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
@@ -61,6 +62,18 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
max.isPresent() ? max.getAsInt() + 1 : 0, mLock);
}
+ @VisibleForTesting
+ IRadioServiceHidlImpl(BroadcastRadioService service,
+ com.android.server.broadcastradio.hal1.BroadcastRadioService hal1,
+ com.android.server.broadcastradio.hal2.BroadcastRadioService hal2) {
+ mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
+ mHal1 = Objects.requireNonNull(hal1,
+ "Broadcast radio service implementation for HIDL 1 HAL cannot be null");
+ mV1Modules = mHal1.loadModules();
+ mHal2 = Objects.requireNonNull(hal2,
+ "Broadcast radio service implementation for HIDL 2 HAL cannot be null");
+ }
+
@Override
public List<RadioManager.ModuleProperties> listModules() {
mService.enforcePolicyAccess();
@@ -95,8 +108,8 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
if (isDebugEnabled()) {
Slog.d(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
}
- Objects.requireNonNull(enabledTypes);
- Objects.requireNonNull(listener);
+ Objects.requireNonNull(enabledTypes, "Enabled announcement types cannot be null");
+ Objects.requireNonNull(listener, "Announcement listener cannot be null");
mService.enforcePolicyAccess();
synchronized (mLock) {
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 b8af1bfcc254..819b719dd22e 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,6 +16,9 @@
package com.android.server.dreams;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+
+import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -34,8 +37,6 @@ import android.os.UserHandle;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamService;
import android.util.Slog;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -60,7 +61,7 @@ final class DreamController {
private final Context mContext;
private final Handler mHandler;
private final Listener mListener;
- private final IWindowManager mIWindowManager;
+ private final ActivityTaskManager mActivityTaskManager;
private long mDreamStartTime;
private String mSavedStopReason;
@@ -93,7 +94,7 @@ final class DreamController {
mContext = context;
mHandler = handler;
mListener = listener;
- mIWindowManager = WindowManagerGlobal.getWindowManagerService();
+ mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mCloseNotificationShadeIntent.putExtra("reason", "dream");
}
@@ -229,6 +230,8 @@ final class DreamController {
}
oldDream.releaseWakeLockIfNeeded();
+ mActivityTaskManager.removeRootTasksWithActivityTypes(new int[] {ACTIVITY_TYPE_DREAM});
+
mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 324eefc809e8..06ee7c4d6c28 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
@@ -98,8 +100,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 +171,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 +199,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 +213,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 +273,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 +321,7 @@ final class BatteryController {
if (monitor == null) {
return;
}
- if (monitor.updateBatteryState(eventTime)) {
- notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
- }
+ monitor.onUEvent(eventTime);
}
}
@@ -310,14 +331,7 @@ 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);
}
}
@@ -329,15 +343,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 +389,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 +409,7 @@ final class BatteryController {
return;
}
final long eventTime = SystemClock.uptimeMillis();
- if (monitor.updateBatteryState(eventTime)) {
- notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
- }
+ monitor.onConfiguration(eventTime);
}
}
};
@@ -422,8 +437,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 +448,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;
+ private final State mState;
+ // Represents whether the input device has a sysfs battery node.
+ protected boolean mHasBattery = false;
@Nullable
private UEventBatteryListener mUEventBatteryListener;
@@ -445,26 +460,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;
+ private 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 +504,44 @@ 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();
+ }
+
+ private 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;
+ }
+
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
return new State(mState);
@@ -503,8 +549,31 @@ 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 {
+
+ UsiDeviceMonitor(int deviceId) {
+ super(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;
}
}
@@ -548,18 +617,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);
+ }
+ }
+
+ private 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 +653,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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 76331fd6089c..76495b17c984 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -58,7 +58,6 @@ import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.annotation.AnyThread;
import android.annotation.BinderThread;
-import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
import android.annotation.EnforcePermission;
@@ -69,9 +68,6 @@ import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -94,7 +90,6 @@ import android.inputmethodservice.InputMethodService;
import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
@@ -170,8 +165,6 @@ import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.inputmethod.UnbindReason;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -255,13 +248,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private static final String HANDLER_THREAD_NAME = "android.imms";
/**
- * A protected broadcast intent action for internal use for {@link PendingIntent} in
- * the notification.
- */
- private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
- "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
-
- /**
* When set, {@link #startInputUncheckedLocked} will return
* {@link InputBindResult#NO_EDITOR} instead of starting an IME connection
* unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides
@@ -334,13 +320,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
private int mDisplayIdToShowIme = INVALID_DISPLAY;
- // Ongoing notification
- private NotificationManager mNotificationManager;
@Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
- private final Notification.Builder mImeSwitcherNotification;
- private final PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
- private boolean mNotificationShown;
@GuardedBy("ImfLock.class")
private final HandwritingModeController mHwController;
@GuardedBy("ImfLock.class")
@@ -1253,17 +1234,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return;
} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
onActionLocaleChanged();
- } else if (ACTION_SHOW_INPUT_METHOD_PICKER.equals(action)) {
- // ACTION_SHOW_INPUT_METHOD_PICKER action is a protected-broadcast and it is
- // guaranteed to be send only from the system, so that there is no need for extra
- // security check such as
- // {@link #canShowInputMethodPickerLocked(IInputMethodClient)}.
- mHandler.obtainMessage(
- MSG_SHOW_IM_SUBTYPE_PICKER,
- // TODO(b/120076400): Design and implement IME switcher for heterogeneous
- // navbar configuration.
- InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES,
- DEFAULT_DISPLAY).sendToTarget();
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -1720,27 +1690,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
- Bundle extras = new Bundle();
- extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
- @ColorInt final int accentColor = mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color);
- mImeSwitcherNotification =
- new Notification.Builder(mContext, SystemNotificationChannels.VIRTUAL_KEYBOARD)
- .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
- .setWhen(0)
- .setOngoing(true)
- .addExtras(extras)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setColor(accentColor);
-
- Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
- .setPackage(mContext.getPackageName());
- mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
- PendingIntent.FLAG_IMMUTABLE);
-
mShowOngoingImeSwitcherForPhones = false;
- mNotificationShown = false;
final int userId = mActivityManagerInternal.getCurrentUserId();
mLastSwitchUserId = userId;
@@ -1939,7 +1890,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final int currentUserId = mSettings.getCurrentUserId();
mSettings.switchCurrentUser(currentUserId,
!mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId));
- mNotificationManager = mContext.getSystemService(NotificationManager.class);
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1977,7 +1927,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED);
broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED);
broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED);
- broadcastFilterForSystemUser.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
broadcastFilterForSystemUser);
@@ -3159,41 +3108,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
}
- final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
- if (imi != null && needsToShowImeSwitcher) {
- // Used to load label
- final CharSequence title = mRes.getText(
- com.android.internal.R.string.select_input_method);
- final int currentUserId = mSettings.getCurrentUserId();
- final Context userAwareContext = mContext.getUserId() == currentUserId
- ? mContext
- : mContext.createContextAsUser(UserHandle.of(currentUserId), 0 /* flags */);
- final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
- userAwareContext, imi, mCurrentSubtype);
- mImeSwitcherNotification.setContentTitle(title)
- .setContentText(summary)
- .setContentIntent(mImeSwitchPendingIntent);
- // TODO(b/120076400): Figure out what is the best behavior
- if ((mNotificationManager != null)
- && !mWindowManagerInternal.hasNavigationBar(DEFAULT_DISPLAY)) {
- if (DEBUG) {
- Slog.d(TAG, "--- show notification: label = " + summary);
- }
- mNotificationManager.notifyAsUser(null,
- SystemMessage.NOTE_SELECT_INPUT_METHOD,
- mImeSwitcherNotification.build(), UserHandle.ALL);
- mNotificationShown = true;
- }
- } else {
- if (mNotificationShown && mNotificationManager != null) {
- if (DEBUG) {
- Slog.d(TAG, "--- hide notification");
- }
- mNotificationManager.cancelAsUser(null,
- SystemMessage.NOTE_SELECT_INPUT_METHOD, UserHandle.ALL);
- mNotificationShown = false;
- }
- }
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index c7ff8caf176b..ebf9237d61ea 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -179,16 +179,6 @@ final class InputMethodUtils {
}
}
- static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
- InputMethodSubtype subtype) {
- final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
- return subtype != null
- ? TextUtils.concat(subtype.getDisplayName(context,
- imi.getPackageName(), imi.getServiceInfo().applicationInfo),
- (TextUtils.isEmpty(imiLabel) ? "" : " - " + imiLabel))
- : imiLabel;
- }
-
/**
* Returns true if a package name belongs to a UID.
*
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 77fea09b5ecc..f459c0e5eeb4 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -10700,10 +10701,18 @@ public class NotificationManagerService extends SystemService {
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>
mRequestedNotificationListeners = new ArrayMap<>();
+ private final boolean mIsHeadlessSystemUserMode;
public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
IPackageManager pm) {
+ this(context, lock, userProfiles, pm, UserManager.isHeadlessSystemUserMode());
+ }
+
+ @VisibleForTesting
+ public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
+ IPackageManager pm, boolean isHeadlessSystemUserMode) {
super(context, lock, userProfiles, pm);
+ this.mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
}
@Override
@@ -10728,10 +10737,16 @@ public class NotificationManagerService extends SystemService {
if (TextUtils.isEmpty(listeners[i])) {
continue;
}
+ int packageQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ // In the headless system user mode, packages might not be installed for the
+ // system user. Match packages for any user since apps can be installed only for
+ // non-system users and would be considering uninstalled for the system user.
+ if (mIsHeadlessSystemUserMode) {
+ packageQueryFlags += MATCH_ANY_USER;
+ }
ArraySet<ComponentName> approvedListeners =
- this.queryPackageForServices(listeners[i],
- MATCH_DIRECT_BOOT_AWARE
- | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM);
+ this.queryPackageForServices(listeners[i], packageQueryFlags,
+ USER_SYSTEM);
for (int k = 0; k < approvedListeners.size(); k++) {
ComponentName cn = approvedListeners.valueAt(k);
addDefaultComponentOrPackage(cn.flattenToString());
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 8e672c3b32c5..17bb39c945bd 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -166,6 +166,14 @@ final class OverlayManagerServiceImpl {
CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
(info) -> !userPackages.containsKey(info.packageName), newUserId));
+ final ArraySet<String> overlaidByOthers = new ArraySet<>();
+ for (AndroidPackage androidPackage : userPackages.values()) {
+ final String overlayTarget = androidPackage.getOverlayTarget();
+ if (!TextUtils.isEmpty(overlayTarget)) {
+ overlaidByOthers.add(overlayTarget);
+ }
+ }
+
// Update the state of all installed packages containing overlays, and initialize new
// overlays that are not currently in the settings.
for (int i = 0, n = userPackages.size(); i < n; i++) {
@@ -175,8 +183,10 @@ final class OverlayManagerServiceImpl {
updatePackageOverlays(pkg, newUserId, 0 /* flags */));
// When a new user is switched to for the first time, package manager must be
- // informed of the overlay paths for all packages installed in the user.
- updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+ // informed of the overlay paths for all overlaid packages installed in the user.
+ if (overlaidByOthers.contains(pkg.getPackageName())) {
+ updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+ }
} catch (OperationFailedException e) {
Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
+ "' for user " + newUserId + "", e);
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/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3f04264714e4..c4f6836eba7b 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
@@ -34,6 +35,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PK
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -56,9 +58,16 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import dalvik.system.DexFile;
@@ -72,11 +81,15 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
-final class DexOptHelper {
+/**
+ * Helper class for dex optimization operations in PackageManagerService.
+ */
+public final class DexOptHelper {
private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
private final PackageManagerService mPm;
@@ -405,11 +418,12 @@ final class DexOptHelper {
* {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
- @PackageDexOptimizer.DexOptResult
+ @DexOptResult
/* package */ int performDexOptWithStatus(DexoptOptions options) {
return performDexOptTraced(options);
}
+ @DexOptResult
private int performDexOptTraced(DexoptOptions options) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
@@ -421,7 +435,13 @@ final class DexOptHelper {
// Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
// if the package can now be considered up to date for the given filter.
+ @DexOptResult
private int performDexOptInternal(DexoptOptions options) {
+ Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+ if (artSrvRes.isPresent()) {
+ return artSrvRes.get();
+ }
+
AndroidPackage p;
PackageSetting pkgSetting;
synchronized (mPm.mLock) {
@@ -446,8 +466,74 @@ final class DexOptHelper {
}
}
- private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
- @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
+ /**
+ * Performs dexopt on the given package using ART Service.
+ *
+ * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
+ * necessary to fall back to the legacy code paths.
+ */
+ private Optional<Integer> performDexOptWithArtService(DexoptOptions options) {
+ ArtManagerLocal artManager = getArtManagerLocal();
+ if (artManager == null) {
+ return Optional.empty();
+ }
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ getPackageManagerLocal().withFilteredSnapshot()) {
+ PackageState ops = snapshot.getPackageState(options.getPackageName());
+ if (ops == null) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ AndroidPackage oap = ops.getAndroidPackage();
+ if (oap == null) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ if (oap.isApex()) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
+ }
+
+ // TODO(b/245301593): Delete the conditional when ART Service supports
+ // FLAG_SHOULD_INCLUDE_DEPENDENCIES and we can just set it unconditionally.
+ /*@OptimizeFlags*/ int extraFlags = ops.getUsesLibraries().isEmpty()
+ ? 0
+ : ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+
+ OptimizeParams params = options.convertToOptimizeParams(extraFlags);
+ if (params == null) {
+ return Optional.empty();
+ }
+
+ // TODO(b/251903639): Either remove controlDexOptBlocking, or don't ignore it here.
+ OptimizeResult result;
+ try {
+ result = artManager.optimizePackage(snapshot, options.getPackageName(), params);
+ } catch (UnsupportedOperationException e) {
+ reportArtManagerFallback(options.getPackageName(), e.toString());
+ return Optional.empty();
+ }
+
+ // TODO(b/251903639): Move this to ArtManagerLocal.addOptimizePackageDoneCallback when
+ // it is implemented.
+ for (OptimizeResult.PackageOptimizeResult pkgRes : result.getPackageOptimizeResults()) {
+ PackageState ps = snapshot.getPackageState(pkgRes.getPackageName());
+ AndroidPackage ap = ps != null ? ps.getAndroidPackage() : null;
+ if (ap != null) {
+ CompilerStats.PackageStats stats = mPm.getOrCreateCompilerPackageStats(ap);
+ for (OptimizeResult.DexContainerFileOptimizeResult dexRes :
+ pkgRes.getDexContainerFileOptimizeResults()) {
+ stats.setCompileTime(
+ dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis());
+ }
+ }
+ }
+
+ return Optional.of(convertToDexOptResult(result));
+ }
+ }
+
+ @DexOptResult
+ private int performDexOptInternalWithDependenciesLI(
+ AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
// System server gets a special path.
if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
return mPm.getDexManager().dexoptSystemServer(options);
@@ -514,10 +600,20 @@ final class DexOptHelper {
// Whoever is calling forceDexOpt wants a compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
- new DexoptOptions(packageName, REASON_CMDLINE,
- getDefaultCompilerFilter(), null /* splitName */,
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+ DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
+ getDefaultCompilerFilter(), null /* splitName */,
+ DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
+
+ // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
+ // the package checks above, but at worst the effect is only a bit less friendly error
+ // below.
+ Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+ int res;
+ if (artSrvRes.isPresent()) {
+ res = artSrvRes.get();
+ } else {
+ res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
+ }
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -800,4 +896,59 @@ final class DexOptHelper {
}
return false;
}
+
+ private @NonNull PackageManagerLocal getPackageManagerLocal() {
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Called whenever we need to fall back from ART Service to the legacy dexopt code.
+ */
+ public static void reportArtManagerFallback(String packageName, String reason) {
+ // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
+ // paths that will always bypass ART Service.
+ Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
+ }
+
+ /**
+ * Returns {@link ArtManagerLocal} if one is found and should be used for package optimization.
+ */
+ private @Nullable ArtManagerLocal getArtManagerLocal() {
+ if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+ return null;
+ }
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Converts an ART Service {@link OptimizeResult} to {@link DexOptResult}.
+ *
+ * For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager.
+ */
+ @DexOptResult
+ private static int convertToDexOptResult(OptimizeResult result) {
+ /*@OptimizeStatus*/ int status = result.getFinalStatus();
+ switch (status) {
+ case OptimizeResult.OPTIMIZE_SKIPPED:
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ case OptimizeResult.OPTIMIZE_FAILED:
+ return PackageDexOptimizer.DEX_OPT_FAILED;
+ case OptimizeResult.OPTIMIZE_PERFORMED:
+ return PackageDexOptimizer.DEX_OPT_PERFORMED;
+ case OptimizeResult.OPTIMIZE_CANCELLED:
+ return PackageDexOptimizer.DEX_OPT_CANCELLED;
+ default:
+ throw new IllegalArgumentException("OptimizeResult for "
+ + result.getPackageOptimizeResults().get(0).getPackageName()
+ + " has unsupported status " + status);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index d25bca76245b..2a2410fd1767 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -652,12 +652,6 @@ public class PackageDexOptimizer {
@DexOptResult
private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
- if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
- // We are asked to optimize only the dex files used by other apps and this is not
- // on of them: skip it.
- return DEX_OPT_SKIPPED;
- }
-
String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
dexUseInfo.isUsedByOtherApps());
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 60f247843bb7..21191919c8e0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2633,6 +2633,9 @@ public class UserManagerService extends IUserManager.Stub {
/** @return a specific user restriction that's in effect currently. */
@Override
public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
+ if (!userExists(userId)) {
+ return false;
+ }
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction");
return mLocalService.hasUserRestriction(restrictionKey, userId);
}
@@ -5516,6 +5519,13 @@ public class UserManagerService extends IUserManager.Stub {
private void removeUserState(final @UserIdInt int userId) {
Slog.i(LOG_TAG, "Removing user state of user " + userId);
+
+ // Cleanup lock settings. This must happen before destroyUserKey(), since the user's DE
+ // storage must still be accessible for the lock settings state to be properly cleaned up.
+ mLockPatternUtils.removeUser(userId);
+
+ // Evict and destroy the user's CE and DE encryption keys. At this point, the user's CE and
+ // DE storage is made inaccessible, except to delete its contents.
try {
mContext.getSystemService(StorageManager.class).destroyUserKey(userId);
} catch (IllegalStateException e) {
@@ -5523,9 +5533,6 @@ public class UserManagerService extends IUserManager.Stub {
Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
}
- // Cleanup lock settings
- mLockPatternUtils.removeUser(userId);
-
// Cleanup package manager settings
mPm.cleanUpUser(this, userId);
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index ea233161b4af..f5557c417f1b 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,6 +18,16 @@ package com.android.server.pm.dex;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+import android.annotation.Nullable;
+
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.pm.DexOptHelper;
+import com.android.server.pm.PackageManagerService;
+
+import dalvik.system.DexFile;
+
/**
* Options used for dexopt invocations.
*/
@@ -40,10 +50,6 @@ public final class DexoptOptions {
// will only consider the primary apk.
public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3;
- // When set, dexopt will optimize only dex files that are used by other apps.
- // Currently, this flag is ignored for primary apks.
- public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4;
-
// When set, dexopt will attempt to scale down the optimizations previously applied in order
// save disk space.
public static final int DEXOPT_DOWNGRADE = 1 << 5;
@@ -105,7 +111,6 @@ public final class DexoptOptions {
DEXOPT_FORCE |
DEXOPT_BOOT_COMPLETE |
DEXOPT_ONLY_SECONDARY_DEX |
- DEXOPT_ONLY_SHARED_DEX |
DEXOPT_DOWNGRADE |
DEXOPT_AS_SHARED_LIBRARY |
DEXOPT_IDLE_BACKGROUND_JOB |
@@ -146,10 +151,6 @@ public final class DexoptOptions {
return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0;
}
- public boolean isDexoptOnlySharedDex() {
- return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0;
- }
-
public boolean isDowngrade() {
return (mFlags & DEXOPT_DOWNGRADE) != 0;
}
@@ -198,4 +199,133 @@ public final class DexoptOptions {
mSplitName,
mFlags);
}
+
+ /**
+ * Returns an {@link OptimizeParams} instance corresponding to this object, for use with
+ * {@link com.android.server.art.ArtManagerLocal}.
+ *
+ * @param extraFlags extra {@link ArtFlags#OptimizeFlags} to set in the returned
+ * {@code OptimizeParams} beyond those converted from this object
+ * @return null if the settings cannot be accurately represented, and hence the old
+ * PackageManager/installd code paths need to be used.
+ */
+ public @Nullable OptimizeParams convertToOptimizeParams(/*@OptimizeFlags*/ int extraFlags) {
+ if (mSplitName != null) {
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "Request to optimize only split " + mSplitName);
+ return null;
+ }
+
+ /*@OptimizeFlags*/ int flags = extraFlags;
+ if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
+ && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) {
+ // ART Service doesn't support bypassing this, so not setting this flag is not
+ // supported.
+ DexOptHelper.reportArtManagerFallback(mPackageName,
+ "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter");
+ return null;
+ }
+ if ((mFlags & DEXOPT_FORCE) != 0) {
+ flags |= ArtFlags.FLAG_FORCE;
+ }
+ if ((mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0) {
+ flags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
+ } else {
+ flags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
+ }
+ if ((mFlags & DEXOPT_DOWNGRADE) != 0) {
+ flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
+ }
+ if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
+ // ART Service cannot be instructed to ignore a DM file if present, so not setting this
+ // flag is not supported.
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
+ return null;
+ }
+
+ /*@PriorityClassApi*/ int priority;
+ // Replicates logic in RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags in installd.
+ if ((mFlags & DEXOPT_BOOT_COMPLETE) != 0) {
+ if ((mFlags & DEXOPT_FOR_RESTORE) != 0) {
+ priority = ArtFlags.PRIORITY_INTERACTIVE_FAST;
+ } else {
+ // TODO(b/251903639): Repurpose DEXOPT_IDLE_BACKGROUND_JOB to choose new
+ // dalvik.vm.background-dex2oat-* properties.
+ priority = ArtFlags.PRIORITY_INTERACTIVE;
+ }
+ } else {
+ priority = ArtFlags.PRIORITY_BOOT;
+ }
+
+ // The following flags in mFlags are ignored:
+ //
+ // - DEXOPT_AS_SHARED_LIBRARY: It's implicit with ART Service since it always looks at
+ // <uses-library> rather than actual dependencies.
+ //
+ // We don't require it to be set either. It's safe when switching between old and new
+ // code paths since the only effect is that some packages may be unnecessarily compiled
+ // without user profiles.
+ //
+ // - DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to
+ // be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc).
+
+ String reason;
+ switch (mCompilationReason) {
+ case PackageManagerService.REASON_FIRST_BOOT:
+ reason = ReasonMapping.REASON_FIRST_BOOT;
+ break;
+ case PackageManagerService.REASON_BOOT_AFTER_OTA:
+ reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
+ break;
+ case PackageManagerService.REASON_POST_BOOT:
+ // This reason will go away with the legacy dexopt code.
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "Unsupported compilation reason REASON_POST_BOOT");
+ return null;
+ case PackageManagerService.REASON_INSTALL:
+ reason = ReasonMapping.REASON_INSTALL;
+ break;
+ case PackageManagerService.REASON_INSTALL_FAST:
+ reason = ReasonMapping.REASON_INSTALL_FAST;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK:
+ reason = ReasonMapping.REASON_INSTALL_BULK;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
+ reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
+ reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+ reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ break;
+ case PackageManagerService.REASON_BACKGROUND_DEXOPT:
+ reason = ReasonMapping.REASON_BG_DEXOPT;
+ break;
+ case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
+ reason = ReasonMapping.REASON_INACTIVE;
+ break;
+ case PackageManagerService.REASON_CMDLINE:
+ reason = ReasonMapping.REASON_CMDLINE;
+ break;
+ case PackageManagerService.REASON_SHARED:
+ case PackageManagerService.REASON_AB_OTA:
+ // REASON_SHARED shouldn't go into this code path - it's only used at lower levels
+ // in PackageDexOptimizer.
+ // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
+ // either.
+ throw new UnsupportedOperationException(
+ "ART Service unsupported compilation reason " + mCompilationReason);
+ default:
+ throw new IllegalArgumentException(
+ "Invalid compilation reason " + mCompilationReason);
+ }
+
+ return new OptimizeParams.Builder(reason, flags)
+ .setCompilerFilter(mCompilerFilter)
+ .setPriorityClass(priority)
+ .build();
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d39b64936bb3..3e0533f1e14b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4193,7 +4193,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;
}
@@ -4726,11 +4728,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 5abc875a697f..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);
@@ -6768,6 +6727,11 @@ public final class PowerManagerService extends SystemService
public void nap(long eventTime, boolean allowWake) {
napInternal(eventTime, Process.SYSTEM_UID, allowWake);
}
+
+ @Override
+ public boolean isAmbientDisplaySuppressed() {
+ return mAmbientDisplaySuppressionController.isSuppressed();
+ }
}
/**
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/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 8ac4fd4860bb..141be702ee13 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -65,6 +65,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
public final DeviceVibrationEffectAdapter deviceEffectAdapter;
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+ // Not guarded by lock because they're not modified by this conductor, it's used here only to
+ // check immutable attributes. The status and other mutable states are changed by the service or
+ // by the vibrator steps.
private final Vibration mVibration;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -412,6 +415,16 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
}
+ /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
+ public boolean wasNotifiedToCancel() {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(false);
+ }
+ synchronized (mLock) {
+ return mSignalCancel != null;
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasPendingNotifySignalLocked() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 8514e272e250..8613b5027d57 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -864,8 +864,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
Vibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded()) {
- // Current vibration is finishing up, it should not block incoming vibrations.
+ if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
+ // Current vibration has ended or is cancelling, should not block incoming vibrations.
return null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 214a2c197a5c..3c457e1cc277 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2578,6 +2578,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
targetActivity.applyOptionsAnimation();
+ if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
+ targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
+ }
} finally {
mActivityMetricsLogger.notifyActivityLaunched(launchingState,
START_TASK_TO_FRONT, false /* newActivityCreated */,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e9774479233d..30399ed15f7e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -588,6 +588,7 @@ class BackNavigationController {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
+ activity.mTaskSupervisor.mStoppingActivities.remove(activity);
activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
0 /* configChanges */, false /* preserveWindows */, true);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 32feb6c98b24..c206a15503de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -613,15 +613,6 @@ public abstract class WindowManagerInternal {
@NonNull IBinder imeTargetWindowToken);
/**
- * Returns the presence of a software navigation bar on the specified display.
- *
- * @param displayId the id of display to check if there is a software navigation bar.
- * @return {@code true} if there is a software navigation. {@code false} otherwise, including
- * the case when the specified display does not exist.
- */
- public abstract boolean hasNavigationBar(int displayId);
-
- /**
* Returns true when the hardware keyboard is available.
*/
public abstract boolean isHardKeyboardAvailable();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c17af3093e51..c9d3dac104de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7917,11 +7917,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean hasNavigationBar(int displayId) {
- return WindowManagerService.this.hasNavigationBar(displayId);
- }
-
- @Override
public boolean isHardKeyboardAvailable() {
synchronized (mGlobalLock) {
return mHardKeyboardAvailable;
@@ -8703,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/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/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 90b1f4ecdcb3..b7e66f23a706 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -39,6 +39,8 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Bundle;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.Settings;
@@ -289,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);
}
/**
@@ -386,4 +398,86 @@ public class BroadcastQueueModernImplTest {
assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
assertTrue(queue.isEmpty());
}
+
+ /**
+ * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected.
+ */
+ @Test
+ public void testDeliveryGroupPolicy_mostRecent() {
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+ optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+ musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_MUSIC);
+ final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
+ optionsMusicVolumeChanged.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ optionsMusicVolumeChanged.setDeliveryGroupKey("audio",
+ String.valueOf(AudioManager.STREAM_MUSIC));
+
+ final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+ alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioManager.STREAM_ALARM);
+ final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
+ optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ optionsAlarmVolumeChanged.setDeliveryGroupKey("audio",
+ String.valueOf(AudioManager.STREAM_ALARM));
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // Verify that the older musicVolumeChanged has been removed.
+ verifyPendingRecords(queue,
+ List.of(timeTick, alarmVolumeChanged, musicVolumeChanged));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ // Verify that the older alarmVolumeChanged has been removed.
+ verifyPendingRecords(queue,
+ List.of(timeTick, musicVolumeChanged, alarmVolumeChanged));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+ optionsMusicVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+ optionsAlarmVolumeChanged));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ // Verify that the older timeTick has been removed.
+ verifyPendingRecords(queue,
+ List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
+ }
+
+ private void verifyPendingRecords(BroadcastProcessQueue queue,
+ List<Intent> intents) {
+ for (int i = 0; i < intents.size(); i++) {
+ queue.makeActiveNextPending();
+ final Intent actualIntent = queue.getActive().intent;
+ final Intent expectedIntent = intents.get(i);
+ final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent
+ + ", actual_extras=" + actualIntent.getExtras()
+ + ", expected_extras=" + expectedIntent.getExtras();
+ assertTrue(errMsg, actualIntent.filterEquals(expectedIntent));
+ assertTrue(errMsg, Bundle.kindofEquals(
+ actualIntent.getExtras(), expectedIntent.getExtras()));
+ }
+ assertTrue(queue.isEmpty());
+ }
}
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/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index 581a2a71a9b6..2d7d46f83c47 100644
--- a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
@@ -254,6 +255,9 @@ public class BackupTransportClientTest {
ITransportStatusCallback c) throws RemoteException {}
@Override public void abortFullRestore(ITransportStatusCallback c) throws RemoteException {}
@Override public void getTransportFlags(AndroidFuture<Integer> f) throws RemoteException {}
+ @Override
+ public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture)
+ throws RemoteException {}
@Override public IBinder asBinder() {
return null;
}
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/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 73548a3a1132..1b5db0a35449 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@ import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -74,6 +75,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -131,6 +133,8 @@ public class FingerprintAuthenticationClientTest {
private Probe mLuxProbe;
@Mock
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private Clock mClock;
@Captor
private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@Captor
@@ -451,6 +455,52 @@ public class FingerprintAuthenticationClientTest {
}
@Test
+ public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ final int vendorAcquireMessage = 1234;
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+ FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+ vendorAcquireMessage);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
+ @Test
+ public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ final int vendorAcquireMessage = 1234;
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+ FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+ vendorAcquireMessage);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1);
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+ }
+
+ @Test
public void sideFingerprintSendsAuthIfFingerUp() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
@@ -497,6 +547,79 @@ public class FingerprintAuthenticationClientTest {
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception {
+ final int powerWindow = 500;
+ final long authStart = 300;
+
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ // Acquire start occurs at time = 0ms
+ when(mClock.millis()).thenReturn(0L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // Auth occurs at time = 300
+ when(mClock.millis()).thenReturn(authStart);
+ // At this point the delay should be 500 - (300 - 0) == 200 milliseconds.
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ // After waiting 200 milliseconds, auth should succeed.
+ mLooper.moveTimeForward(powerWindow - authStart);
+ mLooper.dispatchAll();
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
+ @Test
+ public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception {
+ final int powerWindow = 500;
+
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ // Acquire start occurs at time = 0ms
+ when(mClock.millis()).thenReturn(0L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // Auth reject occurs at time = 300ms
+ when(mClock.millis()).thenReturn(300L);
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ false /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(300);
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ when(mClock.millis()).thenReturn(1300L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // If code is correct, the new acquired start timestamp should be used
+ // and the code should only have to wait 500 - (1500-1300)ms.
+ when(mClock.millis()).thenReturn(1500L);
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(299);
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ mLooper.moveTimeForward(1);
+ mLooper.dispatchAll();
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
@@ -524,7 +647,7 @@ public class FingerprintAuthenticationClientTest {
null /* taskStackListener */, mLockoutCache,
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
mSensorProps,
- new Handler(mLooper.getLooper()), 0 /* biometricStrength */) {
+ new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 6b8c26d7b1d4..d2f2af1b91b6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.companion.virtual;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +27,7 @@ import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -88,6 +91,30 @@ public class InputControllerTest {
}
@Test
+ public void registerInputDevice_deviceCreation_hasDeviceId() {
+ final IBinder device1Token = new Binder("device1");
+ mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token,
+ /* displayId= */ 1);
+ int device1Id = mInputController.getInputDeviceId(device1Token);
+
+ final IBinder device2Token = new Binder("device2");
+ mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2,
+ device2Token, 2);
+ int device2Id = mInputController.getInputDeviceId(device2Token);
+
+ assertWithMessage("Different devices should have different id").that(
+ device1Id).isNotEqualTo(device2Id);
+
+
+ int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+ assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
+ deviceIds).asList().contains(device1Id);
+ assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
+ deviceIds).asList().contains(device2Id);
+
+ }
+
+ @Test
public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() {
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
@@ -115,4 +142,5 @@ public class InputControllerTest {
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
}
+
}
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 9c5d1a5b0610..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
@@ -121,6 +121,7 @@ public class VirtualDeviceManagerServiceTest {
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
private static final String PHYS = "phys";
+ private static final int DEVICE_ID = 42;
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
@@ -530,6 +531,16 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualKeyboard_inputDeviceId_obtainFromInputController() {
+ final int fd = 1;
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */ 1, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
+ assertWithMessage(
+ "InputController should return device id from InputDeviceDescriptor").that(
+ mInputController.getInputDeviceId(BINDER)).isEqualTo(DEVICE_ID);
+ }
+
+ @Test
public void onAudioSessionStarting_hasVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
@@ -576,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));
+ 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);
@@ -601,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));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -616,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));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -642,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));
+ 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());
@@ -656,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));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -683,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));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
@@ -698,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));
+ mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
+ DEVICE_ID);
assertThrows(
IllegalStateException.class,
() ->
@@ -731,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));
+ 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,
@@ -750,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));
+ 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/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 65076a372b0b..b095a506af21 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -32,6 +32,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 +43,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 +66,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 +127,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 +139,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 +168,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 +195,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 +208,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 +239,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 +344,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 +376,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 +401,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 +413,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 +444,44 @@ 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)
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 94e67d16acab..3f55f1bb76a4 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -16,16 +16,16 @@
package com.android.server.om;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +43,9 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
private static final String OVERLAY2 = OVERLAY + "2";
private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
+ @Rule
+ public final Expect expect = Expect.create();
+
@Test
public void alwaysInitializeAllPackages() {
final OverlayManagerServiceImpl impl = getImpl();
@@ -51,13 +54,11 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
addPackage(target(otherTarget), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER),
- new PackageAndUser(otherTarget, USER),
- new PackageAndUser(OVERLAY, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ // The result should be the same for every time
+ assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
+ assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
}
@Test
@@ -66,29 +67,31 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
addPackage(target(TARGET), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o1);
- assertFalse(o1.isEnabled());
- assertFalse(o1.isMutable);
+ expect.that(o1).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o1.isEnabled()).isFalse();
+ expect.that(o1.isMutable).isFalse();
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o2);
- assertTrue(o2.isEnabled());
- assertFalse(o2.isMutable);
+ expect.that(o2).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o2.isEnabled()).isTrue();
+ expect.that(o2.isMutable).isFalse();
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o3);
- assertFalse(o3.isEnabled());
- assertFalse(o3.isMutable);
+ expect.that(o3).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o3.isEnabled()).isFalse();
+ expect.that(o3.isMutable).isFalse();
}
@Test
@@ -98,28 +101,30 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
addPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o1);
- assertFalse(o1.isEnabled());
- assertTrue(o1.isMutable);
+ expect.that(o1).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o1.isEnabled()).isFalse();
+ expect.that(o1.isMutable).isTrue();
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_ENABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o2);
- assertFalse(o2.isEnabled());
- assertTrue(o2.isMutable);
+ expect.that(o2).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o2.isEnabled()).isFalse();
+ expect.that(o2.isMutable).isTrue();
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o3);
- assertFalse(o3.isEnabled());
- assertTrue(o3.isMutable);
+ expect.that(o3).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o3.isEnabled()).isFalse();
+ expect.that(o3.isMutable).isTrue();
}
@Test
@@ -128,17 +133,17 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
addPackage(target(TARGET), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
final Consumer<ConfigState> setOverlay = (state -> {
configureSystemOverlay(OVERLAY, state, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o);
- assertEquals(o.isEnabled(), state == ConfigState.IMMUTABLE_ENABLED
+ expect.that(o).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o.isEnabled()).isEqualTo(state == ConfigState.IMMUTABLE_ENABLED
|| state == ConfigState.MUTABLE_ENABLED);
- assertEquals(o.isMutable, state == ConfigState.MUTABLE_DISABLED
+ expect.that(o.isMutable).isEqualTo(state == ConfigState.MUTABLE_DISABLED
|| state == ConfigState.MUTABLE_ENABLED);
});
@@ -180,20 +185,20 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER),
- new PackageAndUser(OVERLAY2, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o1);
- assertEquals(0, o1.priority);
- assertFalse(o1.isEnabled());
+ expect.that(o1).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o1.priority).isEqualTo(0);
+ expect.that(o1.isEnabled()).isFalse();
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
- assertNotNull(o2);
- assertEquals(1, o2.priority);
- assertFalse(o2.isEnabled());
+ expect.that(o2).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o2.priority).isEqualTo(1);
+ expect.that(o2.isEnabled()).isFalse();
// Overlay priority changing between reboots should not affect enable state of mutable
// overlays.
@@ -202,16 +207,18 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
// Reorder the overlays
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 1 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o3);
- assertEquals(1, o3.priority);
- assertTrue(o3.isEnabled());
+ expect.that(o3).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o3.priority).isEqualTo(1);
+ expect.that(o3.isEnabled()).isTrue();
final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
- assertNotNull(o4);
- assertEquals(0, o4.priority);
- assertFalse(o4.isEnabled());
+ expect.that(o4).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o4.priority).isEqualTo(0);
+ expect.that(o4.isEnabled()).isFalse();
}
@Test
@@ -223,33 +230,35 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);
- final Set<PackageAndUser> allPackages =
- Set.of(new PackageAndUser(TARGET, USER), new PackageAndUser(OVERLAY, USER),
- new PackageAndUser(OVERLAY2, USER));
+ final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o1);
- assertEquals(0, o1.priority);
- assertTrue(o1.isEnabled());
+ expect.that(o1).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o1.priority).isEqualTo(0);
+ expect.that(o1.isEnabled()).isTrue();
final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
- assertNotNull(o2);
- assertEquals(1, o2.priority);
- assertTrue(o2.isEnabled());
+ expect.that(o2).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o2.priority).isEqualTo(1);
+ expect.that(o2.isEnabled()).isTrue();
// Reorder the overlays
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
- assertEquals(allPackages, impl.updateOverlaysForUser(USER));
+ expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
- assertNotNull(o3);
- assertEquals(1, o3.priority);
- assertTrue(o3.isEnabled());
+ expect.that(o3).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o3.priority).isEqualTo(1);
+ expect.that(o3.isEnabled()).isTrue();
final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
- assertNotNull(o4);
- assertEquals(0, o4.priority);
- assertTrue(o4.isEnabled());
+ expect.that(o4).isNotNull();
+ assertThat(expect.hasFailures()).isFalse();
+ expect.that(o4.priority).isEqualTo(0);
+ expect.that(o4.isEnabled()).isTrue();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 96707fde8edb..00aa52012e59 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -176,6 +176,13 @@ public class UserManagerServiceTest {
}
@Test
+ public void testHasUserRestriction_NonExistentUserReturnsFalse() {
+ int nonExistentUserId = UserHandle.USER_NULL;
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, nonExistentUserId))
+ .isFalse();
+ }
+
+ @Test
public void testSetUserRestrictionWithIncorrectID() throws Exception {
int incorrectId = 1;
while (mUserManagerService.userExists(incorrectId)) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index d5893c8d0b9f..77d542a2e43d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -52,7 +52,6 @@ public class DexoptOptionsTests {
assertFalse(opt.isBootComplete());
assertFalse(opt.isCheckForProfileUpdates());
assertFalse(opt.isDexoptOnlySecondaryDex());
- assertFalse(opt.isDexoptOnlySharedDex());
assertFalse(opt.isDowngrade());
assertFalse(opt.isForce());
assertFalse(opt.isDexoptIdleBackgroundJob());
@@ -67,7 +66,6 @@ public class DexoptOptionsTests {
DexoptOptions.DEXOPT_BOOT_COMPLETE |
DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
- DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
DexoptOptions.DEXOPT_DOWNGRADE |
DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |
DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB |
@@ -81,7 +79,6 @@ public class DexoptOptionsTests {
assertTrue(opt.isBootComplete());
assertTrue(opt.isCheckForProfileUpdates());
assertTrue(opt.isDexoptOnlySecondaryDex());
- assertTrue(opt.isDexoptOnlySharedDex());
assertTrue(opt.isDowngrade());
assertTrue(opt.isForce());
assertTrue(opt.isDexoptAsSharedLibrary());
@@ -113,7 +110,6 @@ public class DexoptOptionsTests {
assertTrue(opt.isBootComplete());
assertTrue(opt.isCheckForProfileUpdates());
assertFalse(opt.isDexoptOnlySecondaryDex());
- assertFalse(opt.isDexoptOnlySharedDex());
assertFalse(opt.isDowngrade());
assertTrue(opt.isForce());
assertFalse(opt.isDexoptAsSharedLibrary());
@@ -131,7 +127,6 @@ public class DexoptOptionsTests {
assertTrue(opt.isBootComplete());
assertFalse(opt.isCheckForProfileUpdates());
assertFalse(opt.isDexoptOnlySecondaryDex());
- assertFalse(opt.isDexoptOnlySharedDex());
assertFalse(opt.isDowngrade());
assertTrue(opt.isForce());
assertFalse(opt.isDexoptAsSharedLibrary());
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/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 235849c1cd8b..c484f457faea 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -53,7 +53,8 @@ final class FakeVibratorControllerProvider {
private boolean mIsAvailable = true;
private boolean mIsInfoLoadSuccessful = true;
- private long mLatency;
+ private long mOnLatency;
+ private long mOffLatency;
private int mOffCount;
private int mCapabilities;
@@ -97,7 +98,7 @@ final class FakeVibratorControllerProvider {
public long on(long milliseconds, long vibrationId) {
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) milliseconds));
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(milliseconds, vibrationId);
return milliseconds;
}
@@ -105,12 +106,13 @@ final class FakeVibratorControllerProvider {
@Override
public void off() {
mOffCount++;
+ applyLatency(mOffLatency);
}
@Override
public void setAmplitude(float amplitude) {
mAmplitudes.add(amplitude);
- applyLatency();
+ applyLatency(mOnLatency);
}
@Override
@@ -121,7 +123,7 @@ final class FakeVibratorControllerProvider {
}
recordEffectSegment(vibrationId,
new PrebakedSegment((int) effect, false, (int) strength));
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(EFFECT_DURATION, vibrationId);
return EFFECT_DURATION;
}
@@ -141,7 +143,7 @@ final class FakeVibratorControllerProvider {
duration += EFFECT_DURATION + primitive.getDelay();
recordEffectSegment(vibrationId, primitive);
}
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(duration, vibrationId);
return duration;
}
@@ -154,7 +156,7 @@ final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId, primitive);
}
recordBraking(vibrationId, braking);
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(duration, vibrationId);
return duration;
}
@@ -193,10 +195,10 @@ final class FakeVibratorControllerProvider {
return mIsInfoLoadSuccessful;
}
- private void applyLatency() {
+ private void applyLatency(long latencyMillis) {
try {
- if (mLatency > 0) {
- Thread.sleep(mLatency);
+ if (latencyMillis > 0) {
+ Thread.sleep(latencyMillis);
}
} catch (InterruptedException e) {
}
@@ -240,10 +242,15 @@ final class FakeVibratorControllerProvider {
/**
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
- * it's vibration amplitude.
+ * the vibration amplitude.
*/
- public void setLatency(long millis) {
- mLatency = millis;
+ public void setOnLatency(long millis) {
+ mOnLatency = millis;
+ }
+
+ /** Sets the latency this controller should fake for turning the vibrator off. */
+ public void setOffLatency(long millis) {
+ mOffLatency = millis;
}
/** Set the capabilities of the fake vibrator hardware. */
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index a15e4b0c74a0..fc830a9f61ad 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1159,7 +1159,7 @@ public class VibrationThreadTest {
// 25% of the first waveform step will be spent on the native on() call.
// 25% of each waveform step will be spent on the native setAmplitude() call..
- mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4);
+ mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
int stepCount = totalDuration / stepDuration;
@@ -1190,7 +1190,7 @@ public class VibrationThreadTest {
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long latency = 5_000; // 5s
- fakeVibrator.setLatency(latency);
+ fakeVibrator.setOnLatency(latency);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1204,8 +1204,7 @@ public class VibrationThreadTest {
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_USER),
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false));
cancellingThread.start();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c46fecd1a55e..c83afb74ccba 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -826,13 +826,40 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// No segment played is the prebaked CLICK from the second vibration.
- assertFalse(
- mVibratorProviders.get(1).getAllEffectSegments().stream()
- .anyMatch(segment -> segment instanceof PrebakedSegment));
+ assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
cancelVibrate(service); // Clean up repeating effect.
}
@Test
+ public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
+ throws Exception {
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{255, 0}, 1);
+ vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, wait until the off waveform step.
+ assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+ // Cancel vibration right before requesting a new one.
+ // This should trigger slow IVibrator.off before setting the vibration status to cancelled.
+ cancelVibrate(service);
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ ALARM_ATTRS);
+
+ // Check that second vibration was played.
+ assertTrue(fakeVibrator.getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
+ }
+
+ @Test
public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -880,10 +907,8 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// The second vibration shouldn't have played any prebaked segment.
- assertFalse(
- mVibratorProviders.get(1).getAllEffectSegments().stream()
- .anyMatch(segment -> segment instanceof PrebakedSegment));
-
+ assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
cancelVibrate(service); // Clean up long effect.
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 91c2fe0eb262..8e81e2d8997c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -1371,6 +1371,39 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
+ @Test
+ public void dreamWhenDocked_ambientModeSuppressed_suppressionEnabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
+ @Test
+ public void dreamWhenDocked_ambientModeSuppressed_suppressionDisabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(false);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
+ @Test
+ public void dreamWhenDocked_ambientModeNotSuppressed_suppressionEnabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(false);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
private void triggerDockIntent() {
final Intent dockedIntent =
new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 1e945776cf40..248a3fc8b3b1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -30,9 +31,11 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -49,6 +52,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerFilter;
@@ -69,6 +73,7 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -77,6 +82,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
import java.util.List;
public class NotificationListenersTest extends UiServiceTestCase {
@@ -85,6 +91,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
private PackageManager mPm;
@Mock
private IPackageManager miPm;
+ @Mock
+ private Resources mResources;
@Mock
NotificationManagerService mNm;
@@ -96,7 +104,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");
private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2");
-
+ private ComponentName mUninstalledComponent = new ComponentName("pkg3",
+ "pkg3.NotificationListenerService");
@Before
public void setUp() throws Exception {
@@ -111,7 +120,7 @@ public class NotificationListenersTest extends UiServiceTestCase {
@Test
public void testReadExtraTag() throws Exception {
- String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+ String xml = "<" + TAG_REQUESTED_LISTENERS + ">"
+ "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
+ "<allowed types=\"7\" />"
+ "</listener>"
@@ -131,11 +140,55 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
+ public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception {
+ // setup with headless system user mode
+ mListeners = spy(mNm.new NotificationListeners(
+ mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+ /* isHeadlessSystemUserMode= */ true));
+ mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+ mListeners.loadDefaultsFromConfig();
+
+ assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent);
+ }
+
+ @Test
+ public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled()
+ throws Exception {
+ // setup without headless system user mode
+ mListeners = spy(mNm.new NotificationListeners(
+ mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+ /* isHeadlessSystemUserMode= */ false));
+ mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+ mListeners.loadDefaultsFromConfig();
+
+ assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent);
+ }
+
+ private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) {
+ ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName));
+ when(mResources
+ .getString(
+ com.android.internal.R.string.config_defaultListenerAccessPackages))
+ .thenReturn(componentName.getPackageName());
+ when(mContext.getResources()).thenReturn(mResources);
+ doReturn(components).when(mListeners).queryPackageForServices(
+ eq(componentName.getPackageName()),
+ intThat(hasIntBitFlag(MATCH_ANY_USER)),
+ anyInt());
+ }
+
+ public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) {
+ return arg -> arg != null && ((arg & flag) == flag);
+ }
+
+ @Test
public void testWriteExtraTag() throws Exception {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
deleted file mode 100644
index d7650420788c..000000000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright (C) 2017 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.notification;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.UiServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NotificationTest extends UiServiceTestCase {
-
- @Mock
- ActivityManager mAm;
-
- @Mock
- Resources mResources;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testDoesNotStripsExtenders() {
- Notification.Builder nb = new Notification.Builder(mContext, "channel");
- nb.extend(new Notification.CarExtender().setColor(Color.RED));
- nb.extend(new Notification.TvExtender().setChannelId("different channel"));
- nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
- Notification before = nb.build();
- Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
-
- assertTrue(before == after);
-
- assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
- assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
- assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_noStyles() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test");
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
-
- assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test");
- Notification.Builder n2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_changeStyle() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle());
- Notification.Builder n2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testInboxTextChange() {
- Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
- Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
- }
-
- @Test
- public void testBigTextTextChange() {
- Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle().bigText("something"));
- Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle().bigText("else"));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
- }
-
- @Test
- public void testBigPictureChange() {
- Bitmap bitA = mock(Bitmap.class);
- when(bitA.getGenerationId()).thenReturn(100);
- Bitmap bitB = mock(Bitmap.class);
- when(bitB.getGenerationId()).thenReturn(200);
-
- Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
- Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
- }
-
- @Test
- public void testMessagingChange_text() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class)))
- .addMessage(new Notification.MessagingStyle.Message(
- "b", 100, mock(Person.class)))
- );
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_data() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))
- .setData("text", mock(Uri.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_sender() {
- Person a = mock(Person.class);
- when(a.getName()).thenReturn("A");
- Person b = mock(Person.class);
- when(b.getName()).thenReturn("b");
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_key() {
- Person a = mock(Person.class);
- when(a.getKey()).thenReturn("A");
- Person b = mock(Person.class);
- when(b.getKey()).thenReturn("b");
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_ignoreTimeChange() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 1000, mock(Person.class)))
- );
-
- assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testRemoteViews_nullChange() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setContent(mock(RemoteViews.class));
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test")
- .setContent(mock(RemoteViews.class));
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test")
- .setCustomBigContentView(mock(RemoteViews.class));
- n2 = new Notification.Builder(mContext, "test");
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test")
- .setCustomBigContentView(mock(RemoteViews.class));
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test");
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_layoutChange() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(189);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_layoutSame() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_sequenceChange() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- when(a.getSequenceNumber()).thenReturn(1);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
- when(b.getSequenceNumber()).thenReturn(2);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_sequenceSame() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- when(a.getSequenceNumber()).thenReturn(1);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
- when(b.getSequenceNumber()).thenReturn(1);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testActionsDifferent_null() {
- Notification n1 = new Notification.Builder(mContext, "test")
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentSame() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentText() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
- .build();
-
- assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentSpannables() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon,
- new SpannableStringBuilder().append("test1",
- new StyleSpan(Typeface.BOLD),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
- intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentNumber() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
- .build();
-
- assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentIntent() {
- PendingIntent intent1 = mock(PendingIntent.class);
- PendingIntent intent2 = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsIgnoresRemoteInputs() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(new RemoteInput.Builder("a")
- .setChoices(new CharSequence[] {"i", "m"})
- .build())
- .build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(new RemoteInput.Builder("a")
- .setChoices(new CharSequence[] {"t", "m"})
- .build())
- .build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_noRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .build())
- .build();
- assertNull(notification.findRemoteInputActionPair(false));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_hasRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- RemoteInput remoteInput = new RemoteInput.Builder("a").build();
-
- Notification.Action actionWithRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(remoteInput)
- .addRemoteInput(remoteInput)
- .build();
-
- Notification.Action actionWithoutRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 2", intent)
- .build();
-
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(actionWithoutRemoteInput)
- .addAction(actionWithRemoteInput)
- .build();
-
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(false);
-
- assertNotNull(remoteInputActionPair);
- assertEquals(remoteInput, remoteInputActionPair.first);
- assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(
- new RemoteInput.Builder("a")
- .setAllowFreeFormInput(false).build())
- .build())
- .build();
- assertNull(notification.findRemoteInputActionPair(true));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- RemoteInput remoteInput =
- new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
- RemoteInput freeformRemoteInput =
- new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
-
- Notification.Action actionWithFreeformRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(remoteInput)
- .addRemoteInput(freeformRemoteInput)
- .build();
-
- Notification.Action actionWithoutFreeformRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 2", intent)
- .addRemoteInput(remoteInput)
- .build();
-
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(actionWithoutFreeformRemoteInput)
- .addAction(actionWithFreeformRemoteInput)
- .build();
-
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(true);
-
- assertNotNull(remoteInputActionPair);
- assertEquals(freeformRemoteInput, remoteInputActionPair.first);
- assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
- }
-}
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index d5e336b1cf2f..eed32d7d815c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -40,14 +40,18 @@ 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.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
+import android.app.ActivityOptions;
import android.app.WaitResult;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.Binder;
import android.os.ConditionVariable;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -308,4 +312,40 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
waitHandlerIdle(mAtm.mH);
verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked");
}
+
+ /** Verifies that launch from recents sets the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_withLaunchCookie() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ IBinder launchCookie = new Binder("test_launch_cookie");
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchCookie(launchCookie);
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+ assertThat(activity.mLaunchCookie).isEqualTo(launchCookie);
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ }
+
+ /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_withoutLaunchCookie() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+ assertThat(activity.mLaunchCookie).isNull();
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ }
}
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/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f43f0a53650b..d314a6579b58 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8658,11 +8658,12 @@ public class CarrierConfigManager {
/**
* Boolean indicating if the VoNR setting is visible in the Call Settings menu.
- * If true, the VoNR setting menu will be visible. If false, the menu will be gone.
+ * If this flag is set and VoNR is enabled for this carrier (see {@link #KEY_VONR_ENABLED_BOOL})
+ * the VoNR setting menu will be visible. If {@link #KEY_VONR_ENABLED_BOOL} or
+ * this setting is false, the menu will be gone.
*
- * Disabled by default.
+ * Enabled by default.
*
- * @hide
*/
public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
@@ -8672,7 +8673,6 @@ public class CarrierConfigManager {
*
* Disabled by default.
*
- * @hide
*/
public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
@@ -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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f3d48a849b0f..97a464cb76da 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;
@@ -17115,11 +17116,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 +17129,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 +17141,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 +17180,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 +17211,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 +17265,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 +17288,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 +17305,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 +17334,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 +17352,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 +17371,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/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml
index 5a135c978343..7fe4bae2a3fe 100644
--- a/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml
+++ b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sample.rollbackapp" >
- <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" />
+ <uses-permission android:name="android.permission.MANAGE_ROLLBACKS" />
<application
android:label="@string/title_activity_main">
<activity
@@ -28,4 +28,4 @@
</intent-filter>
</activity>
</application>
-</manifest> \ No newline at end of file
+</manifest>
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);
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
index 68a450d956a8..1b0f03564c3b 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -26,6 +26,7 @@ import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.aidl.hasPermissionMethodAnnotation
import com.intellij.psi.PsiType
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UBlockExpression
@@ -149,11 +150,6 @@ class PermissionMethodDetector : Detector(), SourceCodeScanner {
enabledByDefault = false
)
- private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
- .any {
- it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
- }
-
private fun isPermissionMethodReturnType(method: UMethod): Boolean =
listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 510611161ea8..d120e1d41c99 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -18,37 +18,54 @@ package com.google.android.lint.aidl
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Location
-import com.intellij.psi.PsiVariable
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.ULiteralExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.USimpleNameReferenceExpression
-import org.jetbrains.uast.asRecursiveLogString
+import org.jetbrains.uast.evaluateString
+import org.jetbrains.uast.visitor.AbstractUastVisitor
/**
- * Helper ADT class that facilitates the creation of lint auto fixes
+ * Helper class that facilitates the creation of lint auto fixes
*
* Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
* that should be migrated to @EnforcePermission(allOf={...})
*
* TODO: handle anyOf style annotations
*/
-sealed class EnforcePermissionFix {
- abstract fun locations(): List<Location>
- abstract fun javaAnnotationParameter(): String
-
- fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
+data class EnforcePermissionFix(
+ val locations: List<Location>,
+ val permissionNames: List<String>
+) {
+ val annotation: String
+ get() {
+ val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
+ val annotationParameter =
+ if (permissionNames.size > 1) "allOf={$quotedPermissions}" else quotedPermissions
+ return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
+ }
companion object {
- fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
- SingleFix(
- getPermissionCheckLocation(context, callExpression),
- getPermissionCheckArgumentValue(callExpression)
- )
+ /**
+ * conditionally constructs EnforcePermissionFix from a UCallExpression
+ * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
+ */
+ fun fromCallExpression(
+ context: JavaContext,
+ callExpression: UCallExpression
+ ): EnforcePermissionFix? =
+ if (isPermissionMethodCall(callExpression)) {
+ EnforcePermissionFix(
+ listOf(getPermissionCheckLocation(context, callExpression)),
+ getPermissionCheckValues(callExpression)
+ )
+ } else null
- fun maybeAddManifestPrefix(permissionName: String): String =
- if (permissionName.contains(".")) permissionName
- else "android.Manifest.permission.$permissionName"
+
+ fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
+ EnforcePermissionFix(
+ individuals.flatMap { it.locations },
+ individuals.flatMap { it.permissionNames }
+ )
/**
* Given a permission check, get its proper location
@@ -70,49 +87,51 @@ sealed class EnforcePermissionFix {
}
/**
- * Given a permission check and an argument,
- * pull out the permission value that is being used
+ * Given a @PermissionMethod, find arguments annotated with @PermissionName
+ * and pull out the permission value(s) being used. Also evaluates nested calls
+ * to @PermissionMethod(s) in the given method's body.
*/
- private fun getPermissionCheckArgumentValue(
- callExpression: UCallExpression,
- argumentPosition: Int = 0
- ): String {
+ private fun getPermissionCheckValues(
+ callExpression: UCallExpression
+ ): List<String> {
+ if (!isPermissionMethodCall(callExpression)) return emptyList()
- val identifier = when (
- val argument = callExpression.valueArguments.getOrNull(argumentPosition)
- ) {
- is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
- is USimpleNameReferenceExpression ->
- ((selector.resolve() as PsiVariable).computeConstantValue() as String)
+ val result = mutableSetOf<String>() // protect against duplicate permission values
+ val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
+ val bfsQueue = ArrayDeque(listOf(callExpression))
- else -> throw RuntimeException(
- "Couldn't resolve argument: ${selector.asRecursiveLogString()}"
- )
- }
+ // Breadth First Search - evalutaing nested @PermissionMethod(s) in the available
+ // source code for @PermissionName(s).
+ while (bfsQueue.isNotEmpty()) {
+ val current = bfsQueue.removeFirst()
+ visitedCalls.add(current)
+ result.addAll(findPermissions(current))
- is USimpleNameReferenceExpression -> (
- (argument.resolve() as PsiVariable).computeConstantValue() as String)
+ current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() {
+ override fun visitCallExpression(node: UCallExpression): Boolean {
+ if (isPermissionMethodCall(node) && node !in visitedCalls) {
+ bfsQueue.add(node)
+ }
+ return false
+ }
+ })
+ }
- is ULiteralExpression -> argument.value as String
+ return result.toList()
+ }
- else -> throw RuntimeException(
- "Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
- )
- }
+ private fun findPermissions(
+ callExpression: UCallExpression,
+ ): List<String> {
+ val indices = callExpression.resolve()?.getUMethod()
+ ?.uastParameters
+ ?.filter(::hasPermissionNameAnnotation)
+ ?.mapNotNull { it.sourcePsi?.parameterIndex() }
+ ?: emptyList()
- return identifier.substringAfterLast(".")
+ return indices.mapNotNull {
+ callExpression.getArgumentForParameter(it)?.evaluateString()
+ }
}
}
}
-
-data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
- override fun locations(): List<Location> = listOf(this.location)
- override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
-}
-data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
- override fun locations(): List<Location> = this.checks.map { it.location }
- override fun javaAnnotationParameter(): String =
- "allOf={${
- this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
- }}"
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
new file mode 100644
index 000000000000..edbdd8d2adf3
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
+import com.google.android.lint.ANNOTATION_PERMISSION_NAME
+import com.google.android.lint.CLASS_STUB
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+/**
+ * Given a UMethod, determine if this method is
+ * an entrypoint to an interface generated by AIDL,
+ * returning the interface name if so
+ */
+fun getContainingAidlInterface(node: UMethod): String? {
+ if (!isInClassCalledStub(node)) return null
+ for (superMethod in node.findSuperMethods()) {
+ for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+ ?: continue) {
+ if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+ return superMethod.containingClass?.name
+ }
+ }
+ }
+ return null
+}
+
+private fun isInClassCalledStub(node: UMethod): Boolean {
+ (node.containingClass as? PsiAnonymousClass)?.let {
+ return it.baseClassReference.referenceName == CLASS_STUB
+ }
+ return node.containingClass?.extendsList?.referenceElements?.any {
+ it.referenceName == CLASS_STUB
+ } ?: false
+}
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+ val method = callExpression.resolve()?.getUMethod() ?: return false
+ return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+ .any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
+ }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
index 2cea39423f1d..2c53f390128c 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
@@ -25,9 +25,6 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.CLASS_STUB
-import com.google.android.lint.ENFORCE_PERMISSION_METHODS
-import com.intellij.psi.PsiAnonymousClass
import org.jetbrains.uast.UBlockExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
@@ -56,7 +53,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
val body = (node.uastBody as? UBlockExpression) ?: return
val fix = accumulateSimplePermissionCheckFixes(body) ?: return
- val javaRemoveFixes = fix.locations().map {
+ val javaRemoveFixes = fix.locations.map {
fix()
.replace()
.reformat(true)
@@ -67,7 +64,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
}
val javaAnnotateFix = fix()
- .annotate(fix.javaAnnotation())
+ .annotate(fix.annotation)
.range(context.getLocation(node))
.autoFix()
.build()
@@ -77,7 +74,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
context.report(
ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
- fix.locations().last(),
+ fix.locations.last(),
message,
fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
)
@@ -97,14 +94,14 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
*/
private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
EnforcePermissionFix? {
- val singleFixes = mutableListOf<SingleFix>()
+ val singleFixes = mutableListOf<EnforcePermissionFix>()
for (expression in methodBody.expressions) {
singleFixes.add(getPermissionCheckFix(expression) ?: break)
}
return when (singleFixes.size) {
0 -> null
1 -> singleFixes[0]
- else -> AllOfFix(singleFixes)
+ else -> EnforcePermissionFix.compose(singleFixes)
}
}
@@ -113,7 +110,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
* the helper for creating a lint auto fix to @EnforcePermission
*/
private fun getPermissionCheckFix(startingExpression: UElement?):
- SingleFix? {
+ EnforcePermissionFix? {
return when (startingExpression) {
is UQualifiedReferenceExpression -> getPermissionCheckFix(
startingExpression.selector
@@ -121,11 +118,8 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
- is UCallExpression -> {
- return if (isPermissionCheck(startingExpression))
- EnforcePermissionFix.fromCallExpression(startingExpression, context)
- else null
- }
+ is UCallExpression -> return EnforcePermissionFix
+ .fromCallExpression(context, startingExpression)
else -> null
}
@@ -160,40 +154,5 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
),
enabledByDefault = false, // TODO: enable once b/241171714 is resolved
)
-
- private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
- val method = callExpression.resolve() ?: return false
- val className = method.containingClass?.qualifiedName
- return ENFORCE_PERMISSION_METHODS.any {
- it.clazz == className && it.name == method.name
- }
- }
-
- /**
- * given a UMethod, determine if this method is
- * an entrypoint to an interface generated by AIDL,
- * returning the interface name if so
- */
- fun getContainingAidlInterface(node: UMethod): String? {
- if (!isInClassCalledStub(node)) return null
- for (superMethod in node.findSuperMethods()) {
- for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
- ?: continue) {
- if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
- return superMethod.containingClass?.name
- }
- }
- }
- return null
- }
-
- private fun isInClassCalledStub(node: UMethod): Boolean {
- (node.containingClass as? PsiAnonymousClass)?.let {
- return it.baseClassReference.referenceName == CLASS_STUB
- }
- return node.containingClass?.extendsList?.referenceElements?.any {
- it.referenceName == CLASS_STUB
- } ?: false
- }
}
}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
index 1a1c6bc77785..a968f5e6cb1c 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -19,6 +19,7 @@ package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
@@ -42,7 +43,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
private Context mContext;
@Override
public void test() throws android.os.RemoteException {
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
}
"""
@@ -53,8 +54,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expect(
"""
src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
@@ -62,9 +63,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
@@ -5 +5
- + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
@@ -7 +8
- - mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ - mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
"""
)
}
@@ -81,7 +82,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.READ_CONTACTS", "foo");
+ "android.permission.READ_CONTACTS", "foo");
}
};
}
@@ -102,10 +103,49 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
@@ -6 +6
- + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
@@ -8 +9
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.READ_CONTACTS", "foo");
+ - "android.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testConstantEvaluation() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs,
+ manifestStub
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
"""
)
}
@@ -122,9 +162,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.READ_CONTACTS", "foo");
+ "android.permission.READ_CONTACTS", "foo");
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ "android.permission.WRITE_CONTACTS", "foo");
}
};
}
@@ -144,13 +184,13 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expectFixDiffs(
"""
Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
- @@ -6 +6
- + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
@@ -8 +9
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.READ_CONTACTS", "foo");
+ - "android.permission.READ_CONTACTS", "foo");
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ - "android.permission.WRITE_CONTACTS", "foo");
"""
)
}
@@ -166,7 +206,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
long uid = Binder.getCallingUid();
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
}
"""
@@ -177,6 +217,149 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expectClean()
}
+ fun testPermissionHelper() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helper() {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ helper();
+ ~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+ @@ -12 +12
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -14 +15
+ - helper();
+ """
+ )
+ }
+
+ fun testPermissionHelperAllOf() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helper() {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helper();
+ mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
+ @@ -13 +13
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
+ @@ -15 +16
+ - helper();
+ - mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ """
+ )
+ }
+
+
+ fun testPermissionHelperNested() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helperHelper() {
+ helper("android.permission.WRITE_CONTACTS");
+ }
+
+ @android.content.pm.PermissionMethod
+ private void helper(@android.content.pm.PermissionName String extraPermission) {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helperHelper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ helperHelper();
+ ~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
+ @@ -17 +17
+ + @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
+ @@ -19 +20
+ - helperHelper();
+ """
+ )
+ }
+
+
+
companion object {
private val aidlStub: TestFile = java(
"""
@@ -192,7 +375,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
package android.content;
public class Context {
- public void enforceCallingOrSelfPermission(String permission, String message) {}
+ @android.content.pm.PermissionMethod
+ public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
}
"""
).indented()
@@ -206,6 +390,59 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
).indented()
- val stubs = arrayOf(aidlStub, contextStub, binderStub)
+ private val permissionMethodStub: TestFile = java(
+ """
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({METHOD})
+ public @interface PermissionMethod {}
+ """
+ ).indented()
+
+ private val permissionNameStub: TestFile = java(
+ """
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.FIELD;
+ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.ElementType.PARAMETER;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ public @interface PermissionName {}
+ """
+ ).indented()
+
+ private val manifestStub: TestFile = java(
+ """
+ package android;
+
+ public final class Manifest {
+ public static final class permission {
+ public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+ }
+ }
+ """.trimIndent()
+ )
+
+ val stubs = arrayOf(
+ aidlStub,
+ contextStub,
+ binderStub,
+ permissionMethodStub,
+ permissionNameStub
+ )
}
}