summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/Android.bp1
-rw-r--r--apct-tests/perftests/core/AndroidManifest.xml4
-rw-r--r--apct-tests/perftests/core/src/android/os/DisplayPerfTest.java84
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java100
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java50
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java20
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java5
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java19
-rw-r--r--core/api/current.txt15
-rw-r--r--core/api/system-current.txt5
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--core/java/android/app/Notification.java21
-rw-r--r--core/java/android/companion/AssociationRequest.java200
-rw-r--r--core/java/android/companion/WifiDeviceFilter.java130
-rw-r--r--core/java/android/content/om/OverlayManager.java4
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java16
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java6
-rw-r--r--core/java/android/hardware/OverlayProperties.java47
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java286
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java760
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java7
-rw-r--r--core/java/android/inputmethodservice/InkWindow.java3
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/os/BugreportManager.java24
-rw-r--r--core/java/android/os/BugreportParams.java47
-rw-r--r--core/java/android/os/ISystemConfig.aidl5
-rw-r--r--core/java/android/os/SystemConfigManager.java14
-rw-r--r--core/java/android/os/SystemVibrator.java91
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/view/Display.java13
-rw-r--r--core/java/android/view/GestureDetector.java14
-rw-r--r--core/java/android/view/ImeFocusController.java231
-rw-r--r--core/java/android/view/MotionEvent.java21
-rw-r--r--core/java/android/view/ViewRootImpl.java3
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java273
-rw-r--r--core/java/android/widget/TextView.java24
-rw-r--r--core/java/android/window/TransitionInfo.java48
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java61
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java10
-rw-r--r--core/java/com/android/internal/app/chooser/SelectableTargetInfo.java42
-rw-r--r--core/jni/android_media_AudioSystem.cpp5
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp7
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/AndroidManifest.xml29
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java407
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java100
-rw-r--r--core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java259
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java114
-rw-r--r--core/tests/coretests/src/android/os/VibratorTest.java111
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt184
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java47
-rw-r--r--libs/hwui/DeviceInfo.cpp4
-rw-r--r--libs/hwui/DeviceInfo.h4
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp8
-rw-r--r--media/java/android/media/audiopolicy/AudioMixingRule.java32
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java8
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java57
-rw-r--r--media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java4
-rw-r--r--media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java4
-rw-r--r--packages/CompanionDeviceManager/res/layout/list_item_device.xml3
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java25
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt55
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt191
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt39
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt106
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt55
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt26
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt18
-rw-r--r--packages/SettingsLib/Spa/TEST_MAPPING3
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt22
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt5
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt8
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt12
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt18
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt27
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt65
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt30
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt14
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt17
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt17
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt52
-rw-r--r--packages/SettingsLib/SpaPrivileged/TEST_MAPPING10
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/Android.bp2
-rw-r--r--packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml95
-rw-r--r--packages/SettingsLib/res/values/styles.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java5
-rw-r--r--packages/SettingsProvider/AndroidManifest.xml1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java12
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java56
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java96
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java8
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt95
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt25
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt66
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt (renamed from packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt)34
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt34
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt23
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt25
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt14
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt4
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt63
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt204
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt116
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt68
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt (renamed from packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt)50
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt56
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt38
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt29
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt21
-rw-r--r--packages/SystemUI/docs/device-entry/doze.md4
-rw-r--r--packages/SystemUI/docs/device-entry/glossary.md49
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable/bg_smartspace_media_item.xml2
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml4
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay_legacy.xml160
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt7
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt32
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java53
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/ChooserSelector.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/LatencyTester.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/VendorServices.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java727
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java963
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java (renamed from packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java482
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java174
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/SessionTracker.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java1617
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java1493
-rw-r--r--packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt403
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java183
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt)10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt)9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt20
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt8
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt24
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt3
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt43
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt93
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java33
-rw-r--r--services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java124
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java53
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java5
-rw-r--r--services/companion/java/com/android/server/companion/RolesUtils.java11
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/OWNERS1
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/SystemConfig.java (renamed from core/java/com/android/server/SystemConfig.java)4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java3
-rw-r--r--services/core/java/com/android/server/attention/TEST_MAPPING2
-rw-r--r--services/core/java/com/android/server/biometrics/log/ALSProbe.java100
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java13
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricLogger.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java28
-rw-r--r--services/core/java/com/android/server/display/WakelockController.java211
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java7
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java7
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java16
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java122
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java111
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java31
-rw-r--r--services/core/java/com/android/server/wm/Transition.java36
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/java/com/android/server/SystemConfigService.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java197
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java178
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java54
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java159
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java16
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java60
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java24
-rw-r--r--telephony/java/android/telephony/SmsMessage.java28
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java6
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java61
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java11
-rw-r--r--telephony/java/android/telephony/ims/ImsMmTelManager.java27
-rw-r--r--tests/FlickerTests/res/anim/show_hide_show_3000ms.xml (renamed from tests/FlickerTests/res/anim/show_2000ms.xml)20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt28
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt28
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java113
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java40
365 files changed, 11928 insertions, 6271 deletions
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 23464f879518..ab20fdbde1e5 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -43,6 +43,7 @@ android_test {
"apct-perftests-resources-manager-apps",
"apct-perftests-utils",
"collector-device-lib",
+ "compatibility-device-util-axt",
"core-tests-support",
"guava",
],
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 4e24909f5d3b..cd0f1cdf388c 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -13,7 +13,9 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
-
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />
<profileable android:shell="true" />
diff --git a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
new file mode 100644
index 000000000000..0802072ae144
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.os;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.provider.Settings;
+import android.view.Display;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPerfTest {
+ private static final float DELTA = 0.001f;
+
+ @Rule
+ public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+ private DisplayManager mDisplayManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
+ @Test
+ public void testBrightnessChanges() throws Exception {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ SystemClock.sleep(20);
+ float brightness = 0.3f;
+ while (state.keepRunning()) {
+ setAndWaitToChangeBrightness(brightness);
+ brightness = toggleBrightness(brightness);
+ }
+ }
+
+ private float toggleBrightness(float oldBrightness) {
+ float[] brightnesses = new float[]{0.3f, 0.5f};
+ if (oldBrightness == brightnesses[0]) {
+ return brightnesses[1];
+ }
+ return brightnesses[0];
+ }
+
+ private void setAndWaitToChangeBrightness(float brightness) throws Exception {
+ mDisplayManager.setBrightness(0, brightness);
+ PollingCheck.check("Brightness is not set to the expected value", 500,
+ () -> isInRange(mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY), brightness,
+ DELTA));
+ }
+
+ private boolean isInRange(float value, float target, float delta) {
+ return target - delta <= value && target + delta >= value;
+ }
+}
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 2682dd75ca73..442c13009d8b 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -36,9 +36,11 @@ public interface JobSchedulerInternal {
/**
* Cancel the jobs for a given uid (e.g. when app data is cleared)
+ *
+ * @param includeProxiedJobs Include jobs scheduled for this UID by other apps
*/
- void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, int debugReasonCode,
- String debugReason);
+ void cancelJobsForUid(int uid, boolean includeProxiedJobs,
+ @JobParameters.StopReason int reason, int debugReasonCode, String debugReason);
/**
* These are for activity manager to communicate to use what is currently performing backups.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 586038427c8d..20bca3530b63 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -34,6 +34,7 @@ import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.BatteryStats;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -181,6 +182,7 @@ class JobConcurrencyManager {
private final JobSchedulerService mService;
private final Context mContext;
private final Handler mHandler;
+ private final Injector mInjector;
private PowerManager mPowerManager;
@@ -378,9 +380,15 @@ class JobConcurrencyManager {
}
JobConcurrencyManager(JobSchedulerService service) {
+ this(service, new Injector());
+ }
+
+ @VisibleForTesting
+ JobConcurrencyManager(JobSchedulerService service, Injector injector) {
mService = service;
mLock = mService.mLock;
mContext = service.getTestableContext();
+ mInjector = injector;
mHandler = JobSchedulerBackgroundThread.getHandler();
@@ -414,7 +422,7 @@ class JobConcurrencyManager {
ServiceManager.getService(BatteryStats.SERVICE_NAME));
for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) {
mIdleContexts.add(
- new JobServiceContext(mService, this, batteryStats,
+ mInjector.createJobServiceContext(mService, this, batteryStats,
mService.mJobPackageTracker, mContext.getMainLooper()));
}
}
@@ -657,15 +665,40 @@ class JobConcurrencyManager {
return;
}
+ prepareForAssignmentDeterminationLocked(
+ mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+ if (DEBUG) {
+ Slog.d(TAG, printAssignments("running jobs initial",
+ mRecycledStoppable, mRecycledPreferredUidOnly));
+ }
+
+ determineAssignmentsLocked(
+ mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+ if (DEBUG) {
+ Slog.d(TAG, printAssignments("running jobs final",
+ mRecycledStoppable, mRecycledPreferredUidOnly, mRecycledChanged));
+
+ Slog.d(TAG, "work count results: " + mWorkCountTracker);
+ }
+
+ carryOutAssignmentChangesLocked(mRecycledChanged);
+
+ cleanUpAfterAssignmentChangesLocked(
+ mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+ noteConcurrency();
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
+ final List<ContextAssignment> preferredUidOnly,
+ final List<ContextAssignment> stoppable) {
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
- // To avoid GC churn, we recycle the arrays.
- final ArraySet<ContextAssignment> changed = mRecycledChanged;
- final ArraySet<ContextAssignment> idle = mRecycledIdle;
- final ArrayList<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
- final ArrayList<ContextAssignment> stoppable = mRecycledStoppable;
-
updateCounterConfigLocked();
// Reset everything since we'll re-evaluate the current state.
mWorkCountTracker.resetCounts();
@@ -719,15 +752,21 @@ class JobConcurrencyManager {
assignment.context = jsc;
idle.add(assignment);
}
- if (DEBUG) {
- Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly));
- }
mWorkCountTracker.onCountDone();
+ }
- JobStatus nextPending;
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
+ final ArraySet<ContextAssignment> idle,
+ final List<ContextAssignment> preferredUidOnly,
+ final List<ContextAssignment> stoppable) {
+ final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
+ final List<JobServiceContext> activeServices = mActiveServices;
pendingJobQueue.resetIterator();
- int projectedRunningCount = numRunningJobs;
+ JobStatus nextPending;
+ int projectedRunningCount = activeServices.size();
while ((nextPending = pendingJobQueue.next()) != null) {
if (mRunningJobs.contains(nextPending)) {
// Should never happen.
@@ -895,13 +934,10 @@ class JobConcurrencyManager {
packageStats);
}
}
- if (DEBUG) {
- Slog.d(TAG, printAssignments("running jobs final",
- stoppable, preferredUidOnly, changed));
-
- Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
- }
+ }
+ @GuardedBy("mLock")
+ private void carryOutAssignmentChangesLocked(final ArraySet<ContextAssignment> changed) {
for (int c = changed.size() - 1; c >= 0; --c) {
final ContextAssignment assignment = changed.valueAt(c);
final JobStatus js = assignment.context.getRunningJobLocked();
@@ -925,6 +961,13 @@ class JobConcurrencyManager {
assignment.clear();
mContextAssignmentPool.release(assignment);
}
+ }
+
+ @GuardedBy("mLock")
+ private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
+ final ArraySet<ContextAssignment> idle,
+ final List<ContextAssignment> preferredUidOnly,
+ final List<ContextAssignment> stoppable) {
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
assignment.clear();
@@ -947,7 +990,6 @@ class JobConcurrencyManager {
preferredUidOnly.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
- noteConcurrency();
}
@GuardedBy("mLock")
@@ -1496,7 +1538,7 @@ class JobConcurrencyManager {
@NonNull
private JobServiceContext createNewJobServiceContext() {
- return new JobServiceContext(mService, this,
+ return mInjector.createJobServiceContext(mService, this,
IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME)),
mService.mJobPackageTracker, mContext.getMainLooper());
@@ -1778,6 +1820,10 @@ class JobConcurrencyManager {
@VisibleForTesting
static class WorkTypeConfig {
@VisibleForTesting
+ static final String KEY_PREFIX_MAX = CONFIG_KEY_PREFIX_CONCURRENCY + "max_";
+ @VisibleForTesting
+ static final String KEY_PREFIX_MIN = CONFIG_KEY_PREFIX_CONCURRENCY + "min_";
+ @VisibleForTesting
static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_";
@@ -2329,7 +2375,8 @@ class JobConcurrencyManager {
}
}
- private static final class ContextAssignment {
+ @VisibleForTesting
+ static final class ContextAssignment {
public JobServiceContext context;
public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
public int workType = WORK_TYPE_NONE;
@@ -2378,4 +2425,15 @@ class JobConcurrencyManager {
mActivePkgStats.add(userId, packageName, packageStats);
return packageStats;
}
+
+ @VisibleForTesting
+ static class Injector {
+ @NonNull
+ JobServiceContext createJobServiceContext(JobSchedulerService service,
+ JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+ JobPackageTracker tracker, Looper looper) {
+ return new JobServiceContext(service, concurrencyManager, batteryStats,
+ tracker, looper);
+ }
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f5c0ed9f03f7..bdd1fc548af2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -970,12 +970,12 @@ public class JobSchedulerService extends com.android.server.SystemService
// Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped?
if (pkgUid != -1) {
- List<JobStatus> jobsForUid;
+ ArraySet<JobStatus> jobsForUid;
synchronized (mLock) {
jobsForUid = mJobs.getJobsByUid(pkgUid);
}
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
- if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
+ if (jobsForUid.valueAt(i).getSourcePackageName().equals(pkgName)) {
if (DEBUG) {
Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
+ pkgUid + " has jobs");
@@ -1292,10 +1292,11 @@ public class JobSchedulerService extends com.android.server.SystemService
public List<JobInfo> getPendingJobs(int uid) {
synchronized (mLock) {
- List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+ ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+ // Write out for loop to avoid addAll() creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
+ final JobStatus job = jobs.valueAt(i);
outList.add(job.getJob());
}
return outList;
@@ -1304,9 +1305,9 @@ public class JobSchedulerService extends com.android.server.SystemService
public JobInfo getPendingJob(int uid, int jobId) {
synchronized (mLock) {
- List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+ ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
for (int i = jobs.size() - 1; i >= 0; i--) {
- JobStatus job = jobs.get(i);
+ JobStatus job = jobs.valueAt(i);
if (job.getJobId() == jobId) {
return job.getJob();
}
@@ -1348,7 +1349,7 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
return;
}
- final List<JobStatus> jobsForUid = new ArrayList<>();
+ final ArraySet<JobStatus> jobsForUid = new ArraySet<>();
if (includeSchedulingApp) {
mJobs.getJobsByUid(uid, jobsForUid);
}
@@ -1356,7 +1357,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mJobs.getJobsBySourceUid(uid, jobsForUid);
}
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
- final JobStatus job = jobsForUid.get(i);
+ final JobStatus job = jobsForUid.valueAt(i);
final boolean shouldCancel =
(includeSchedulingApp
&& job.getServiceComponent().getPackageName().equals(pkgName))
@@ -1368,14 +1369,16 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
- * Entry point from client to cancel all jobs originating from their uid.
+ * Entry point from client to cancel all jobs scheduled for or from their uid.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
*
* @param uid Uid to check against for removal of a job.
+ * @param includeSourceApp Whether to include jobs scheduled for this UID by another UID.
+ * If false, only jobs scheduled by this UID will be cancelled.
*/
- public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
- int internalReasonCode, String debugReason) {
+ public boolean cancelJobsForUid(int uid, boolean includeSourceApp,
+ @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
@@ -1383,9 +1386,15 @@ public class JobSchedulerService extends com.android.server.SystemService
boolean jobsCanceled = false;
synchronized (mLock) {
- final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
+ final ArraySet<JobStatus> jobsForUid = new ArraySet<>();
+ // Get jobs scheduled by the app.
+ mJobs.getJobsByUid(uid, jobsForUid);
+ if (includeSourceApp) {
+ // Get jobs scheduled for the app by someone else.
+ mJobs.getJobsBySourceUid(uid, jobsForUid);
+ }
for (int i = 0; i < jobsForUid.size(); i++) {
- JobStatus toRemove = jobsForUid.get(i);
+ JobStatus toRemove = jobsForUid.valueAt(i);
cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
jobsCanceled = true;
}
@@ -2220,6 +2229,7 @@ public class JobSchedulerService extends com.android.server.SystemService
updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (disabled) {
cancelJobsForUid(uid,
+ /* includeSourceApp */ true,
JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
"uid gone");
@@ -2241,6 +2251,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final boolean disabled = message.arg2 != 0;
if (disabled) {
cancelJobsForUid(uid,
+ /* includeSourceApp */ true,
JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
"app uid idle");
@@ -2899,9 +2910,10 @@ public class JobSchedulerService extends com.android.server.SystemService
}
@Override
- public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
- int internalReasonCode, String debugReason) {
- JobSchedulerService.this.cancelJobsForUid(uid, reason, internalReasonCode, debugReason);
+ public void cancelJobsForUid(int uid, boolean includeProxiedJobs,
+ @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+ JobSchedulerService.this.cancelJobsForUid(uid,
+ includeProxiedJobs, reason, internalReasonCode, debugReason);
}
@Override
@@ -3273,6 +3285,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid,
+ // Documentation says only jobs scheduled BY the app will be cancelled
+ /* includeSourceApp */ false,
JobParameters.STOP_REASON_CANCELLED_BY_APP,
JobParameters.INTERNAL_STOP_REASON_CANCELED,
"cancelAll() called by app, callingUid=" + uid);
@@ -3484,7 +3498,9 @@ public class JobSchedulerService extends com.android.server.SystemService
if (!hasJobId) {
pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
- if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+ if (!cancelJobsForUid(pkgUid,
+ /* includeSourceApp */ false,
+ JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_CANCELED,
"cancel shell command for package")) {
pw.println("No matching jobs found.");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index ff4d26dc807d..f731b8d1d0d7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -291,11 +291,11 @@ public final class JobStore {
* @return A list of all the jobs scheduled for the source app. Never null.
*/
@NonNull
- public List<JobStatus> getJobsBySourceUid(int sourceUid) {
+ public ArraySet<JobStatus> getJobsBySourceUid(int sourceUid) {
return mJobSet.getJobsBySourceUid(sourceUid);
}
- public void getJobsBySourceUid(int sourceUid, @NonNull List<JobStatus> insertInto) {
+ public void getJobsBySourceUid(int sourceUid, @NonNull Set<JobStatus> insertInto) {
mJobSet.getJobsBySourceUid(sourceUid, insertInto);
}
@@ -304,11 +304,11 @@ public final class JobStore {
* @return All JobStatus objects for a given uid from the master list. Never null.
*/
@NonNull
- public List<JobStatus> getJobsByUid(int uid) {
+ public ArraySet<JobStatus> getJobsByUid(int uid) {
return mJobSet.getJobsByUid(uid);
}
- public void getJobsByUid(int uid, @NonNull List<JobStatus> insertInto) {
+ public void getJobsByUid(int uid, @NonNull Set<JobStatus> insertInto) {
mJobSet.getJobsByUid(uid, insertInto);
}
@@ -1232,13 +1232,13 @@ public final class JobStore {
mJobsPerSourceUid = new SparseArray<>();
}
- public List<JobStatus> getJobsByUid(int uid) {
- ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
+ public ArraySet<JobStatus> getJobsByUid(int uid) {
+ ArraySet<JobStatus> matchingJobs = new ArraySet<>();
getJobsByUid(uid, matchingJobs);
return matchingJobs;
}
- public void getJobsByUid(int uid, List<JobStatus> insertInto) {
+ public void getJobsByUid(int uid, Set<JobStatus> insertInto) {
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs != null) {
insertInto.addAll(jobs);
@@ -1246,13 +1246,13 @@ public final class JobStore {
}
@NonNull
- public List<JobStatus> getJobsBySourceUid(int sourceUid) {
- final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+ public ArraySet<JobStatus> getJobsBySourceUid(int sourceUid) {
+ final ArraySet<JobStatus> result = new ArraySet<>();
getJobsBySourceUid(sourceUid, result);
return result;
}
- public void getJobsBySourceUid(int sourceUid, List<JobStatus> insertInto) {
+ public void getJobsBySourceUid(int sourceUid, Set<JobStatus> insertInto) {
final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
if (jobs != null) {
insertInto.addAll(jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 9c167728cff9..bbd661acca01 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -50,7 +50,6 @@ import com.android.server.utils.AlarmQueue;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.function.Predicate;
/**
@@ -397,10 +396,10 @@ public final class FlexibilityController extends StateController {
return;
}
final long nowElapsed = sElapsedRealtimeClock.millis();
- List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
+ ArraySet<JobStatus> jobsByUid = mService.getJobStore().getJobsBySourceUid(uid);
boolean hasPrefetch = false;
for (int i = 0; i < jobsByUid.size(); i++) {
- JobStatus js = jobsByUid.get(i);
+ JobStatus js = jobsByUid.valueAt(i);
if (js.hasFlexibilityConstraint()) {
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
hasPrefetch |= js.getJob().isPrefetch();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 999a3c02b18c..669234b4a670 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -126,7 +126,7 @@ public final class JobStatus {
/**
* Keeps track of how many flexible constraints must be satisfied for the job to execute.
*/
- private int mNumRequiredFlexibleConstraints;
+ private final int mNumRequiredFlexibleConstraints;
/**
* Number of required flexible constraints that have been dropped.
@@ -343,7 +343,8 @@ public final class JobStatus {
public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
/** Minimum difference between start and end time to have flexible constraint */
- private static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
+ @VisibleForTesting
+ static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
/**
* Versatile, persistable flags for a job that's updated within the system server,
* as opposed to {@link JobInfo#flags} that's set by callers.
@@ -580,6 +581,8 @@ public final class JobStatus {
mNumRequiredFlexibleConstraints =
NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
requiredConstraints |= CONSTRAINT_FLEXIBLE;
+ } else {
+ mNumRequiredFlexibleConstraints = 0;
}
this.requiredConstraints = requiredConstraints;
@@ -1152,7 +1155,7 @@ public final class JobStatus {
/** Returns the number of flexible job constraints required to be satisfied to execute */
public int getNumRequiredFlexibleConstraints() {
- return mNumRequiredFlexibleConstraints;
+ return mNumRequiredFlexibleConstraints - mNumDroppedFlexibleConstraints;
}
/**
@@ -1585,14 +1588,8 @@ public final class JobStatus {
/** Adjusts the number of required flexible constraints by the given number */
public void adjustNumRequiredFlexibleConstraints(int adjustment) {
- mNumRequiredFlexibleConstraints += adjustment;
- if (mNumRequiredFlexibleConstraints < 0) {
- mNumRequiredFlexibleConstraints = 0;
- }
- mNumDroppedFlexibleConstraints -= adjustment;
- if (mNumDroppedFlexibleConstraints < 0) {
- mNumDroppedFlexibleConstraints = 0;
- }
+ mNumDroppedFlexibleConstraints = Math.max(0, Math.min(mNumRequiredFlexibleConstraints,
+ mNumDroppedFlexibleConstraints - adjustment));
}
/**
diff --git a/core/api/current.txt b/core/api/current.txt
index c3f734c0a75b..e5cc625038dd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4749,7 +4749,7 @@ package android.app {
method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
method public int startOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
- method public int startOpNoThrow(@NonNull String, int, @NonNull String, @NonNull String, @Nullable String);
+ method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
method public int startProxyOp(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
method public int startProxyOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
@@ -41598,6 +41598,7 @@ package android.telephony {
field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+ field public static final String KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG = "data_switch_validation_min_gap_long";
field public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG = "data_switch_validation_timeout_long";
field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -43817,7 +43818,7 @@ package android.telephony {
method public void registerTelephonyCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
- method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(int, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE}) public android.telephony.NetworkScan requestNetworkScan(int, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(String);
method public String sendEnvelopeWithStatus(String);
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
@@ -48432,9 +48433,9 @@ package android.view {
public static interface GestureDetector.OnGestureListener {
method public boolean onDown(@NonNull android.view.MotionEvent);
- method public boolean onFling(@NonNull android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
+ method public boolean onFling(@Nullable android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
method public void onLongPress(@NonNull android.view.MotionEvent);
- method public boolean onScroll(@NonNull android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
+ method public boolean onScroll(@Nullable android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
method public void onShowPress(@NonNull android.view.MotionEvent);
method public boolean onSingleTapUp(@NonNull android.view.MotionEvent);
}
@@ -48445,9 +48446,9 @@ package android.view {
method public boolean onDoubleTap(@NonNull android.view.MotionEvent);
method public boolean onDoubleTapEvent(@NonNull android.view.MotionEvent);
method public boolean onDown(@NonNull android.view.MotionEvent);
- method public boolean onFling(@NonNull android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
+ method public boolean onFling(@Nullable android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
method public void onLongPress(@NonNull android.view.MotionEvent);
- method public boolean onScroll(@NonNull android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
+ method public boolean onScroll(@Nullable android.view.MotionEvent, @NonNull android.view.MotionEvent, float, float);
method public void onShowPress(@NonNull android.view.MotionEvent);
method public boolean onSingleTapConfirmed(@NonNull android.view.MotionEvent);
method public boolean onSingleTapUp(@NonNull android.view.MotionEvent);
@@ -49402,6 +49403,8 @@ package android.view {
field public static final int AXIS_GENERIC_7 = 38; // 0x26
field public static final int AXIS_GENERIC_8 = 39; // 0x27
field public static final int AXIS_GENERIC_9 = 40; // 0x28
+ field public static final int AXIS_GESTURE_X_OFFSET = 48; // 0x30
+ field public static final int AXIS_GESTURE_Y_OFFSET = 49; // 0x31
field public static final int AXIS_HAT_X = 15; // 0xf
field public static final int AXIS_HAT_Y = 16; // 0x10
field public static final int AXIS_HSCROLL = 10; // 0xa
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 761d9fa3841f..3dbfdae99d54 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6511,6 +6511,7 @@ package android.media.audiopolicy {
field public static final int MIX_ROLE_PLAYERS = 0; // 0x0
field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1
+ field public static final int RULE_MATCH_AUDIO_SESSION_ID = 16; // 0x10
field public static final int RULE_MATCH_UID = 4; // 0x4
field public static final int RULE_MATCH_USERID = 8; // 0x8
}
@@ -9276,13 +9277,17 @@ package android.os {
}
public final class BugreportManager {
+ method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData();
method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
public final class BugreportParams {
ctor public BugreportParams(int);
+ ctor public BugreportParams(int, int);
+ method public int getFlags();
method public int getMode();
+ field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 39a7ca82c639..d22702f39a5d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1352,6 +1352,7 @@ package android.inputmethodservice {
}
@UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
+ field public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L; // 0x8d39f80L
field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 28404d5338c6..0cb00d98b3b4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8189,7 +8189,7 @@ public class AppOpsManager {
* @see #startOp(String, int, String, String, String)
*/
public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
- @NonNull String attributionTag, @Nullable String message) {
+ @Nullable String attributionTag, @Nullable String message) {
return startOpNoThrow(strOpToOp(op), uid, packageName, false, attributionTag, message);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5695874e6100..74eb1c526777 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9623,21 +9623,16 @@ public class Notification implements Parcelable
@NonNull
public ArrayList<Action> getActionsListWithSystemActions() {
// Define the system actions we expect to see
- final Action negativeAction = makeNegativeAction();
- final Action answerAction = makeAnswerAction();
- // Sort the expected actions into the correct order:
- // * If there's no answer action, put the hang up / decline action at the end
- // * Otherwise put the answer action at the end, and put the decline action at start.
- final Action firstAction = answerAction == null ? null : negativeAction;
- final Action lastAction = answerAction == null ? negativeAction : answerAction;
+ final Action firstAction = makeNegativeAction();
+ final Action lastAction = makeAnswerAction();
// Start creating the result list.
int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
- if (firstAction != null) {
- resultActions.add(firstAction);
- --nonContextualActionSlotsRemaining;
- }
+
+ // Always have a first action.
+ resultActions.add(firstAction);
+ --nonContextualActionSlotsRemaining;
// Copy actions into the new list, correcting system actions.
if (mBuilder.mActions != null) {
@@ -9653,14 +9648,14 @@ public class Notification implements Parcelable
--nonContextualActionSlotsRemaining;
}
// If there's exactly one action slot left, fill it with the lastAction.
- if (nonContextualActionSlotsRemaining == 1) {
+ if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
resultActions.add(lastAction);
--nonContextualActionSlotsRemaining;
}
}
}
// If there are any action slots left, the lastAction still needs to be added.
- if (nonContextualActionSlotsRemaining >= 1) {
+ if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
resultActions.add(lastAction);
}
return resultActions;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index a2277e97c043..3325d1eb2505 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -35,7 +35,6 @@ import android.os.Parcelable;
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -55,13 +54,6 @@ import java.util.Objects;
* You can also set {@link Builder#setSingleDevice single device} to request a popup with single
* device to be shown instead of a list to choose from
*/
-@DataClass(
- genConstructor = false,
- genToString = true,
- genEqualsHashCode = true,
- genHiddenGetters = true,
- genParcelable = true,
- genConstDefs = false)
public final class AssociationRequest implements Parcelable {
/**
* Device profile: watch.
@@ -139,24 +131,28 @@ public final class AssociationRequest implements Parcelable {
/**
* If set, only devices matching either of the given filters will be shown to the user
*/
- @DataClass.PluralOf("deviceFilter")
- private final @NonNull List<DeviceFilter<?>> mDeviceFilters;
+ @NonNull
+ private final List<DeviceFilter<?>> mDeviceFilters;
/**
* Profile of the device.
*/
- private final @Nullable @DeviceProfile String mDeviceProfile;
+ @Nullable
+ @DeviceProfile
+ private final String mDeviceProfile;
/**
* The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
* "self-managed" association.
*/
- private @Nullable CharSequence mDisplayName;
+ @Nullable
+ private CharSequence mDisplayName;
/**
* The device that was associated. Will be null for "self-managed" association.
*/
- private @Nullable AssociatedDevice mAssociatedDevice;
+ @Nullable
+ private AssociatedDevice mAssociatedDevice;
/**
* Whether the association is to be managed by the companion application.
@@ -175,21 +171,24 @@ public final class AssociationRequest implements Parcelable {
* Populated by the system.
* @hide
*/
- private @Nullable String mPackageName;
+ @Nullable
+ private String mPackageName;
/**
* The UserId of the user the association will belong to.
* Populated by the system.
* @hide
*/
- private @UserIdInt int mUserId;
+ @UserIdInt
+ private int mUserId;
/**
* The user-readable description of the device profile's privileges.
* Populated by the system.
* @hide
*/
- private @Nullable String mDeviceProfilePrivilegesDescription;
+ @Nullable
+ private String mDeviceProfilePrivilegesDescription;
/**
* The time at which his request was created
@@ -243,7 +242,9 @@ public final class AssociationRequest implements Parcelable {
/**
* @return profile of the companion device.
*/
- public @Nullable @DeviceProfile String getDeviceProfile() {
+ @Nullable
+ @DeviceProfile
+ public String getDeviceProfile() {
return mDeviceProfile;
}
@@ -251,7 +252,8 @@ public final class AssociationRequest implements Parcelable {
* The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
* "self-managed" association.
*/
- public @Nullable CharSequence getDisplayName() {
+ @Nullable
+ public CharSequence getDisplayName() {
return mDisplayName;
}
@@ -328,9 +330,9 @@ public final class AssociationRequest implements Parcelable {
*/
public static final class Builder extends OneTimeUseBuilder<AssociationRequest> {
private boolean mSingleDevice = false;
- private @Nullable ArrayList<DeviceFilter<?>> mDeviceFilters = null;
- private @Nullable String mDeviceProfile;
- private @Nullable CharSequence mDisplayName;
+ private ArrayList<DeviceFilter<?>> mDeviceFilters = null;
+ private String mDeviceProfile;
+ private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
@@ -432,29 +434,13 @@ public final class AssociationRequest implements Parcelable {
}
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/AssociationRequest.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* The device that was associated. Will be null for "self-managed" association.
*
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable AssociatedDevice getAssociatedDevice() {
+ @Nullable
+ public AssociatedDevice getAssociatedDevice() {
return mAssociatedDevice;
}
@@ -464,8 +450,8 @@ public final class AssociationRequest implements Parcelable {
*
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable String getPackageName() {
+ @Nullable
+ public String getPackageName() {
return mPackageName;
}
@@ -475,8 +461,8 @@ public final class AssociationRequest implements Parcelable {
*
* @hide
*/
- @DataClass.Generated.Member
- public @UserIdInt int getUserId() {
+ @UserIdInt
+ public int getUserId() {
return mUserId;
}
@@ -486,8 +472,8 @@ public final class AssociationRequest implements Parcelable {
*
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable String getDeviceProfilePrivilegesDescription() {
+ @Nullable
+ public String getDeviceProfilePrivilegesDescription() {
return mDeviceProfilePrivilegesDescription;
}
@@ -496,7 +482,6 @@ public final class AssociationRequest implements Parcelable {
*
* @hide
*/
- @DataClass.Generated.Member
public long getCreationTime() {
return mCreationTime;
}
@@ -507,47 +492,34 @@ public final class AssociationRequest implements Parcelable {
*
* @hide
*/
- @DataClass.Generated.Member
public boolean isSkipPrompt() {
return mSkipPrompt;
}
@Override
- @DataClass.Generated.Member
public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "AssociationRequest { " +
- "singleDevice = " + mSingleDevice + ", " +
- "deviceFilters = " + mDeviceFilters + ", " +
- "deviceProfile = " + mDeviceProfile + ", " +
- "displayName = " + mDisplayName + ", " +
- "associatedDevice = " + mAssociatedDevice + ", " +
- "selfManaged = " + mSelfManaged + ", " +
- "forceConfirmation = " + mForceConfirmation + ", " +
- "packageName = " + mPackageName + ", " +
- "userId = " + mUserId + ", " +
- "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " +
- "creationTime = " + mCreationTime + ", " +
- "skipPrompt = " + mSkipPrompt +
- " }";
+ return "AssociationRequest { "
+ + "singleDevice = " + mSingleDevice
+ + ", deviceFilters = " + mDeviceFilters
+ + ", deviceProfile = " + mDeviceProfile
+ + ", displayName = " + mDisplayName
+ + ", associatedDevice = " + mAssociatedDevice
+ + ", selfManaged = " + mSelfManaged
+ + ", forceConfirmation = " + mForceConfirmation
+ + ", packageName = " + mPackageName
+ + ", userId = " + mUserId
+ + ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
+ + ", creationTime = " + mCreationTime
+ + ", skipPrompt = " + mSkipPrompt
+ + " }";
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(AssociationRequest other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
AssociationRequest that = (AssociationRequest) o;
- //noinspection PointlessBooleanExpression
- return true
- && mSingleDevice == that.mSingleDevice
+ return mSingleDevice == that.mSingleDevice
&& Objects.equals(mDeviceFilters, that.mDeviceFilters)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mDisplayName, that.mDisplayName)
@@ -556,17 +528,14 @@ public final class AssociationRequest implements Parcelable {
&& mForceConfirmation == that.mForceConfirmation
&& Objects.equals(mPackageName, that.mPackageName)
&& mUserId == that.mUserId
- && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription)
+ && Objects.equals(mDeviceProfilePrivilegesDescription,
+ that.mDeviceProfilePrivilegesDescription)
&& mCreationTime == that.mCreationTime
&& mSkipPrompt == that.mSkipPrompt;
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + Boolean.hashCode(mSingleDevice);
_hash = 31 * _hash + Objects.hashCode(mDeviceFilters);
@@ -584,21 +553,18 @@ public final class AssociationRequest implements Parcelable {
}
@Override
- @DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
int flg = 0;
if (mSingleDevice) flg |= 0x1;
- if (mSelfManaged) flg |= 0x20;
- if (mForceConfirmation) flg |= 0x40;
- if (mSkipPrompt) flg |= 0x800;
- if (mDeviceProfile != null) flg |= 0x4;
- if (mDisplayName != null) flg |= 0x8;
- if (mAssociatedDevice != null) flg |= 0x10;
+ if (mSelfManaged) flg |= 0x2;
+ if (mForceConfirmation) flg |= 0x4;
+ if (mSkipPrompt) flg |= 0x8;
+ if (mDeviceProfile != null) flg |= 0x10;
+ if (mDisplayName != null) flg |= 0x20;
+ if (mAssociatedDevice != null) flg |= 0x40;
if (mPackageName != null) flg |= 0x80;
- if (mDeviceProfilePrivilegesDescription != null) flg |= 0x200;
+ if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
@@ -606,37 +572,36 @@ public final class AssociationRequest implements Parcelable {
if (mAssociatedDevice != null) dest.writeTypedObject(mAssociatedDevice, flags);
if (mPackageName != null) dest.writeString(mPackageName);
dest.writeInt(mUserId);
- if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
+ if (mDeviceProfilePrivilegesDescription != null) {
+ dest.writeString8(mDeviceProfilePrivilegesDescription);
+ }
dest.writeLong(mCreationTime);
}
@Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
+ public int describeContents() {
+ return 0;
+ }
/** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
+ @SuppressWarnings("unchecked")
/* package-private */ AssociationRequest(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
int flg = in.readInt();
boolean singleDevice = (flg & 0x1) != 0;
- boolean selfManaged = (flg & 0x20) != 0;
- boolean forceConfirmation = (flg & 0x40) != 0;
- boolean skipPrompt = (flg & 0x800) != 0;
+ boolean selfManaged = (flg & 0x2) != 0;
+ boolean forceConfirmation = (flg & 0x4) != 0;
+ boolean skipPrompt = (flg & 0x8) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
(Class<android.companion.DeviceFilter<?>>) (Class<?>)
android.companion.DeviceFilter.class);
- String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
- CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
- AssociatedDevice associatedDevice = (flg & 0x10) == 0
- ? null : (AssociatedDevice) in.readTypedObject(AssociatedDevice.CREATOR);
+ String deviceProfile = (flg & 0x10) == 0 ? null : in.readString();
+ CharSequence displayName = (flg & 0x20) == 0 ? null : in.readCharSequence();
+ AssociatedDevice associatedDevice = (flg & 0x40) == 0 ? null
+ : in.readTypedObject(AssociatedDevice.CREATOR);
String packageName = (flg & 0x80) == 0 ? null : in.readString();
int userId = in.readInt();
- String deviceProfilePrivilegesDescription = (flg & 0x200) == 0 ? null : in.readString();
+ String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString8();
long creationTime = in.readLong();
this.mSingleDevice = singleDevice;
@@ -644,8 +609,6 @@ public final class AssociationRequest implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mDeviceFilters);
this.mDeviceProfile = deviceProfile;
- com.android.internal.util.AnnotationValidations.validate(
- DeviceProfile.class, null, mDeviceProfile);
this.mDisplayName = displayName;
this.mAssociatedDevice = associatedDevice;
this.mSelfManaged = selfManaged;
@@ -657,13 +620,11 @@ public final class AssociationRequest implements Parcelable {
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
this.mSkipPrompt = skipPrompt;
-
- // onConstructed(); // You can define this method to get a callback
}
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<AssociationRequest> CREATOR
- = new Parcelable.Creator<AssociationRequest>() {
+ @NonNull
+ public static final Parcelable.Creator<AssociationRequest> CREATOR =
+ new Parcelable.Creator<AssociationRequest>() {
@Override
public AssociationRequest[] newArray(int size) {
return new AssociationRequest[size];
@@ -674,17 +635,4 @@ public final class AssociationRequest implements Parcelable {
return new AssociationRequest(in);
}
};
-
- @DataClass.Generated(
- time = 1663088980513L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate @android.annotation.Nullable android.companion.AssociatedDevice mAssociatedDevice\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic void setDisplayName(java.lang.CharSequence)\npublic void setAssociatedDevice(android.companion.AssociatedDevice)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 8b48f4c46977..7d2a25a7bcd8 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -27,7 +27,6 @@ import android.net.wifi.ScanResult;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling;
import java.util.Objects;
@@ -38,32 +37,27 @@ import java.util.regex.Pattern;
*
* @see ScanFilter
*/
-@DataClass(
- genParcelable = true,
- genAidl = false,
- genBuilder = true,
- genEqualsHashCode = true,
- genHiddenGetters = true)
public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
/**
* If set, only devices with {@link BluetoothDevice#getName name} matching the given regular
* expression will be shown
*/
- @DataClass.ParcelWith(Parcelling.BuiltIn.ForPattern.class)
- @DataClass.MaySetToNull
- private @Nullable Pattern mNamePattern = null;
+ @Nullable
+ private final Pattern mNamePattern;
/**
* If set, only devices with BSSID matching the given one will be shown
*/
- private @Nullable MacAddress mBssid = null;
+ @Nullable
+ private final MacAddress mBssid;
/**
* If set, only bits at positions set in this mask, will be compared to the given
* {@link Builder#setBssid BSSID} filter.
*/
- private @NonNull MacAddress mBssidMask = MacAddress.BROADCAST_ADDRESS;
+ @NonNull
+ private final MacAddress mBssidMask;
/** @hide */
@Override
@@ -85,22 +79,6 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
return MEDIUM_TYPE_WIFI;
}
-
-
- // Code below generated by codegen v1.0.15.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/WifiDeviceFilter.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
/* package-private */ WifiDeviceFilter(
@Nullable Pattern namePattern,
@Nullable MacAddress bssid,
@@ -110,8 +88,6 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
this.mBssidMask = bssidMask;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBssidMask);
-
- // onConstructed(); // You can define this method to get a callback
}
/**
@@ -120,8 +96,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
*
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable Pattern getNamePattern() {
+ @Nullable
+ public Pattern getNamePattern() {
return mNamePattern;
}
@@ -130,8 +106,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
*
* @hide
*/
- @DataClass.Generated.Member
- public @Nullable MacAddress getBssid() {
+ @Nullable
+ public MacAddress getBssid() {
return mBssid;
}
@@ -141,35 +117,23 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
*
* @hide
*/
- @DataClass.Generated.Member
- public @NonNull MacAddress getBssidMask() {
+ @NonNull
+ public MacAddress getBssidMask() {
return mBssidMask;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(WifiDeviceFilter other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
WifiDeviceFilter that = (WifiDeviceFilter) o;
- //noinspection PointlessBooleanExpression
- return true
- && Objects.equals(mNamePattern, that.mNamePattern)
+ return Objects.equals(mNamePattern, that.mNamePattern)
&& Objects.equals(mBssid, that.mBssid)
&& Objects.equals(mBssidMask, that.mBssidMask);
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + Objects.hashCode(mNamePattern);
_hash = 31 * _hash + Objects.hashCode(mBssid);
@@ -177,10 +141,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
return _hash;
}
- @DataClass.Generated.Member
static Parcelling<Pattern> sParcellingForNamePattern =
- Parcelling.Cache.get(
- Parcelling.BuiltIn.ForPattern.class);
+ Parcelling.Cache.get(Parcelling.BuiltIn.ForPattern.class);
static {
if (sParcellingForNamePattern == null) {
sParcellingForNamePattern = Parcelling.Cache.put(
@@ -189,11 +151,7 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
}
@Override
- @DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
byte flg = 0;
if (mNamePattern != null) flg |= 0x1;
if (mBssid != null) flg |= 0x2;
@@ -204,33 +162,27 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
}
@Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
+ public int describeContents() {
+ return 0;
+ }
/** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
/* package-private */ WifiDeviceFilter(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
byte flg = in.readByte();
Pattern namePattern = sParcellingForNamePattern.unparcel(in);
- MacAddress bssid = (flg & 0x2) == 0 ? null : (MacAddress) in.readTypedObject(MacAddress.CREATOR);
- MacAddress bssidMask = (MacAddress) in.readTypedObject(MacAddress.CREATOR);
+ MacAddress bssid = (flg & 0x2) == 0 ? null : in.readTypedObject(MacAddress.CREATOR);
+ MacAddress bssidMask = in.readTypedObject(MacAddress.CREATOR);
this.mNamePattern = namePattern;
this.mBssid = bssid;
this.mBssidMask = bssidMask;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBssidMask);
-
- // onConstructed(); // You can define this method to get a callback
}
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<WifiDeviceFilter> CREATOR
- = new Parcelable.Creator<WifiDeviceFilter>() {
+ @NonNull
+ public static final Parcelable.Creator<WifiDeviceFilter> CREATOR =
+ new Parcelable.Creator<WifiDeviceFilter>() {
@Override
public WifiDeviceFilter[] newArray(int size) {
return new WifiDeviceFilter[size];
@@ -246,12 +198,11 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
* A builder for {@link WifiDeviceFilter}
*/
@SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
public static final class Builder {
- private @Nullable Pattern mNamePattern;
- private @Nullable MacAddress mBssid;
- private @NonNull MacAddress mBssidMask;
+ @Nullable private Pattern mNamePattern;
+ @Nullable private MacAddress mBssid;
+ @NonNull private MacAddress mBssidMask;
private long mBuilderFieldsSet = 0L;
@@ -262,8 +213,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
* If set, only devices with {@link BluetoothDevice#getName name} matching the given regular
* expression will be shown
*/
- @DataClass.Generated.Member
- public @NonNull Builder setNamePattern(@Nullable Pattern value) {
+ @NonNull
+ public Builder setNamePattern(@Nullable Pattern value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
mNamePattern = value;
@@ -273,8 +224,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
/**
* If set, only devices with BSSID matching the given one will be shown
*/
- @DataClass.Generated.Member
- public @NonNull Builder setBssid(@NonNull MacAddress value) {
+ @NonNull
+ public Builder setBssid(@NonNull MacAddress value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
mBssid = value;
@@ -285,8 +236,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
* If set, only bits at positions set in this mask, will be compared to the given
* {@link Builder#setBssid BSSID} filter.
*/
- @DataClass.Generated.Member
- public @NonNull Builder setBssidMask(@NonNull MacAddress value) {
+ @NonNull
+ public Builder setBssidMask(@NonNull MacAddress value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
mBssidMask = value;
@@ -294,7 +245,8 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
}
/** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull WifiDeviceFilter build() {
+ @NonNull
+ public WifiDeviceFilter build() {
checkNotUsed();
mBuilderFieldsSet |= 0x8; // Mark builder used
@@ -307,11 +259,10 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
if ((mBuilderFieldsSet & 0x4) == 0) {
mBssidMask = MacAddress.BROADCAST_ADDRESS;
}
- WifiDeviceFilter o = new WifiDeviceFilter(
+ return new WifiDeviceFilter(
mNamePattern,
mBssid,
mBssidMask);
- return o;
}
private void checkNotUsed() {
@@ -321,17 +272,4 @@ public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
}
}
}
-
- @DataClass.Generated(
- time = 1582688421965L,
- codegenVersion = "1.0.15",
- sourceFile = "frameworks/base/core/java/android/companion/WifiDeviceFilter.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @com.android.internal.util.DataClass.MaySetToNull @android.annotation.Nullable java.util.regex.Pattern mNamePattern\nprivate @android.annotation.Nullable android.net.MacAddress mBssid\nprivate @android.annotation.NonNull android.net.MacAddress mBssidMask\npublic @java.lang.Override boolean matches(android.net.wifi.ScanResult)\npublic @java.lang.Override java.lang.String getDeviceDisplayName(android.net.wifi.ScanResult)\npublic @java.lang.Override int getMediumType()\nclass WifiDeviceFilter extends java.lang.Object implements [android.companion.DeviceFilter<android.net.wifi.ScanResult>]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=false, genBuilder=true, genEqualsHashCode=true, genHiddenGetters=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 0f7e01b234c2..94275aea0c4d 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -31,8 +31,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import com.android.server.SystemConfig;
-
import java.util.List;
/**
@@ -50,7 +48,7 @@ import java.util.List;
* <overlayable name="OverlayableResourcesName" actor="overlay://namespace/actorName">
* }</pre></p>
*
- * <p>Actors are defined through {@link SystemConfig}. Only system packages can be used.
+ * <p>Actors are defined through SystemConfig. Only system packages can be used.
* The namespace "android" is reserved for use by AOSP and any "android" definitions must
* have an implementation on device that fulfill their intended functionality.</p>
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e13f60c3265b..2c28268d0772 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -45,7 +45,6 @@ import android.window.OnBackInvokedCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Parcelling;
import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
-import com.android.server.SystemConfig;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -803,7 +802,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE = 1 << 2;
-
/**
* If false, {@link android.view.KeyEvent#KEYCODE_BACK} related events will be forwarded to
* the Activities, Dialogs and Views and {@link android.app.Activity#onBackPressed()},
@@ -815,12 +813,24 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK = 1 << 3;
+ /**
+ * Whether or not this package is allowed to access hidden APIs. Replacement for legacy
+ * implementation of {@link #isPackageWhitelistedForHiddenApis()}.
+ *
+ * This is an internal flag and should never be used outside of this class. The real API for
+ * the hidden API enforcement policy is {@link #getHiddenApiEnforcementPolicy()}.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
PRIVATE_FLAG_EXT_PROFILEABLE,
PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION,
PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
+ PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlagsExt {}
@@ -2222,7 +2232,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
private boolean isPackageWhitelistedForHiddenApis() {
- return SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName);
+ return (privateFlagsExt & PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS) != 0;
}
/**
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index eddb519051a9..5de6d73945eb 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -36,6 +36,12 @@ 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/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
new file mode 100644
index 000000000000..2a0956b47a7b
--- /dev/null
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import java.util.List;
+
+/**
+ * // TODO(b/242588489): Continue work, the class needs a jni-specific constructor and DisplayInfo
+ * // side constructs the object.
+ *
+ * @hide
+ */
+public final class OverlayProperties {
+ private final SupportedBufferCombinations[] mCombinations = null;
+ private final boolean mSupportFp16ForHdr = false;
+
+ static class SupportedBufferCombinations {
+ @HardwareBuffer.Format List<Integer> mHardwareBufferFormats;
+ @DataSpace.NamedDataSpace List<Integer> mDataSpaces;
+ SupportedBufferCombinations(@HardwareBuffer.Format List<Integer> hardwareBufferFormats,
+ @DataSpace.NamedDataSpace List<Integer> dataSpaces) {
+ mHardwareBufferFormats = hardwareBufferFormats;
+ mDataSpaces = dataSpaces;
+ }
+ }
+
+ /***
+ * @return if the device can support fp16.
+ */
+ public boolean supportFp16ForHdr() {
+ return mSupportFp16ForHdr;
+ }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 861a8502c44d..8873807b7a98 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2609,31 +2609,31 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <table>
* <thead>
* <tr>
- * <th align="left">Input Format</th>
- * <th align="left">Output Format</th>
- * <th align="left">Capability</th>
+ * <th style="text-align: left;">Input Format</th>
+ * <th style="text-align: left;">Output Format</th>
+ * <th style="text-align: left;">Capability</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
@@ -2650,26 +2650,26 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <table>
* <thead>
* <tr>
- * <th align="left">Input Format</th>
- * <th align="left">Output Format</th>
- * <th align="left">Capability</th>
+ * <th style="text-align: left;">Input Format</th>
+ * <th style="text-align: left;">Output Format</th>
+ * <th style="text-align: left;">Capability</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
@@ -2705,60 +2705,60 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">Any</td>
- * <td align="center">if 720p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 480p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 240p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for JPEG</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for JPEG</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for JPEG, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for JPEG, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">IMPLEMENTATION_DEFINED</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -2773,66 +2773,66 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 720p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 480p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 240p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">IMPLEMENTATION_DEFINED</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -2987,66 +2987,66 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 720p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 480p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 240p &lt;= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3e1deb27584e..1a15596af566 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -990,18 +990,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device auto exposure algorithm is disabled</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device auto exposure algorithm is disabled</td>
* </tr>
* </tbody>
* </table>
@@ -1009,120 +1009,120 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Good values, not changing</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Good values, not changing</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AE scan</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AE scan</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values not good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values not good after unlock</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values good after unlock</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Exposure good, but too dark</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Exposure good, but too dark</td>
* </tr>
* <tr>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Ready for high-quality capture</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Ready for high-quality capture</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">aeLock is ON and aePrecaptureTrigger is START</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is START</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Start AE precapture metering sequence</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Start AE precapture metering sequence</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Currently active precapture metering sequence is canceled</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Currently active precapture metering sequence is canceled</td>
* </tr>
* </tbody>
* </table>
@@ -1138,54 +1138,54 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a new scan, transient states are skipped by camera device.</td>
* </tr>
* </tbody>
* </table>
@@ -1413,18 +1413,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Never changes</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Never changes</td>
* </tr>
* </tbody>
* </table>
@@ -1432,66 +1432,66 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start AF sweep, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF sweep done</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focused, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF sweep done</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focused, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF sweep done</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Not focused, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF sweep done</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Not focused, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start new sweep, Lens now moving</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start new sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start new sweep, Lens now moving</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start new sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
- * <td align="center">Mode change</td>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">Any state</td>
+ * <td style="text-align: center;">Mode change</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -1504,36 +1504,36 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Focus failed after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus failed after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is good after a scan, lens is not locked.</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is good after a scan, lens is not locked.</td>
* </tr>
* </tbody>
* </table>
@@ -1541,102 +1541,102 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF state query, Lens now locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF state query, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device completes current scan</td>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device completes current scan</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device fails current scan</td>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device fails current scan</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, if focus is good. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, if focus is good. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, if focus is bad. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, if focus is bad. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Reset lens position, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Reset lens position, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* </tbody>
* </table>
@@ -1644,102 +1644,102 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF state query, Lens now locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF state query, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device completes current scan</td>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device completes current scan</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device fails current scan</td>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device fails current scan</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Eventual transition once the focus is good. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Eventual transition once the focus is good. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Eventual transition if cannot find focus. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Eventual transition if cannot find focus. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Reset lens position, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Reset lens position, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate trans. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate trans. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate trans. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate trans. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* </tbody>
* </table>
@@ -1751,30 +1751,30 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">CAF--&gt;AUTO mode switch</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">CAF--&gt;AUTO mode switch</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Mode switch without trigger, initial state must be INACTIVE</td>
* </tr>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">CAF--&gt;AUTO mode switch with AF_TRIGGER</td>
- * <td align="center">trigger-reachable states from INACTIVE</td>
- * <td align="center">Mode switch with trigger, INACTIVE is skipped</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">CAF--&gt;AUTO mode switch with AF_TRIGGER</td>
+ * <td style="text-align: center;">trigger-reachable states from INACTIVE</td>
+ * <td style="text-align: center;">Mode switch with trigger, INACTIVE is skipped</td>
* </tr>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">AUTO--&gt;CAF mode switch</td>
- * <td align="center">passively reachable states from INACTIVE</td>
- * <td align="center">Mode switch without trigger, passive transient state is skipped</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">AUTO--&gt;CAF mode switch</td>
+ * <td style="text-align: center;">passively reachable states from INACTIVE</td>
+ * <td style="text-align: center;">Mode switch without trigger, passive transient state is skipped</td>
* </tr>
* </tbody>
* </table>
@@ -2053,18 +2053,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device auto white balance algorithm is disabled</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device auto white balance algorithm is disabled</td>
* </tr>
* </tbody>
* </table>
@@ -2072,54 +2072,54 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates AWB scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates AWB scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AWB scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Good values, not changing</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AWB scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Good values, not changing</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device initiates AWB scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device initiates AWB scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values not good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values not good after unlock</td>
* </tr>
* </tbody>
* </table>
@@ -2132,24 +2132,24 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device finished AWB scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device finished AWB scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values good after unlock, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values good after unlock, transient states are skipped by camera device.</td>
* </tr>
* </tbody>
* </table>
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 9294dea50b0d..79223f5d2414 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -32,6 +32,7 @@ import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.ColorSpace;
import android.graphics.Point;
+import android.hardware.OverlayProperties;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.media.projection.IMediaProjection;
@@ -112,6 +113,7 @@ public final class DisplayManagerGlobal {
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
private final ColorSpace mWideColorSpace;
+ private final OverlayProperties mOverlayProperties = new OverlayProperties();
private int[] mDisplayIdCache;
private int mWifiDisplayScanNestCount;
@@ -726,6 +728,11 @@ public final class DisplayManagerGlobal {
return mWideColorSpace;
}
+ /** @hide */
+ public OverlayProperties getOverlaySupport() {
+ return mOverlayProperties;
+ }
+
/**
* Sets the global brightness configuration for a given user.
*
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 469b52bd9185..70bd5046524e 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -53,6 +53,9 @@ final class InkWindow extends PhoneWindow {
final LayoutParams attrs = getAttributes();
attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0);
+ // disable window animations.
+ // TODO(b/253477462): replace with API when available
+ attrs.windowAnimations = -1;
// TODO(b/210039666): use INPUT_FEATURE_NO_INPUT_CHANNEL once b/216179339 is fixed.
setAttributes(attrs);
// Ink window is not touchable with finger.
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 92c3311fcc27..39d362b17d97 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -565,9 +565,10 @@ public class InputMethodService extends AbstractInputMethodService {
*
* @hide
*/
+ @TestApi
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+ public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
LayoutInflater mInflater;
TypedArray mThemeAttrs;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 73bb8d566500..222e88f9d3e1 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -148,6 +148,29 @@ public final class BugreportManager {
}
/**
+ * Speculatively pre-dumps UI data for a bugreport request that might come later.
+ *
+ * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
+ * ring buffers that might get lost by the time the actual bugreport is requested.
+ *
+ * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following
+ * conditions are met:
+ * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
+ * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @WorkerThread
+ public void preDumpUiData() {
+ try {
+ mBinder.preDumpUiData(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts a bugreport.
*
* <p>This starts a bugreport in the background. However the call itself can take several
@@ -198,6 +221,7 @@ public final class BugreportManager {
bugreportFd.getFileDescriptor(),
screenshotFd.getFileDescriptor(),
params.getMode(),
+ params.getFlags(),
dsListener,
isScreenshotRequested);
} catch (RemoteException e) {
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 279ccae7c94f..990883f6d83c 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -30,16 +30,46 @@ import java.lang.annotation.RetentionPolicy;
@SystemApi
public final class BugreportParams {
private final int mMode;
+ private final int mFlags;
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ */
public BugreportParams(@BugreportMode int mode) {
mMode = mode;
+ mFlags = 0;
}
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ * @param flags to customize the bugreport request
+ */
+ public BugreportParams(@BugreportMode int mode, @BugreportFlag int flags) {
+ mMode = mode;
+ mFlags = flags;
+ }
+
+ /**
+ * Returns the mode of the bugreport to request.
+ */
+ @BugreportMode
public int getMode() {
return mMode;
}
/**
+ * Returns the flags to customize the bugreport request.
+ */
+ @BugreportFlag
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Defines acceptable types of bugreports.
* @hide
*/
@@ -88,4 +118,21 @@ public final class BugreportParams {
* Wifi.
*/
public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+
+ /**
+ * Defines acceptable flags for customizing bugreport requests.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
+ BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ })
+ public @interface BugreportFlag {}
+
+ /**
+ * Flag for reusing pre-dumped UI data. The pre-dump and bugreport request calls must be
+ * performed by the same UID, otherwise the flag is ignored.
+ */
+ public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
+ IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
}
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 15e3ce25122b..61b24aa55e30 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -47,4 +47,9 @@ interface ISystemConfig {
* @see SystemConfigManager#getEnabledComponentOverrides
*/
List<ComponentName> getEnabledComponentOverrides(String packageName);
+
+ /**
+ * @see SystemConfigManager#getDefaultVrComponents
+ */
+ List<ComponentName> getDefaultVrComponents();
}
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index cde2063fdba5..77843d9fbb0a 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -147,4 +147,18 @@ public class SystemConfigManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Return the components that are enabled by default as VR mode listener services.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)
+ public List<ComponentName> getDefaultVrComponents() {
+ try {
+ return mInterface.getDefaultVrComponents();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0aafaf456a08..6091bf9088d3 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -50,10 +50,10 @@ public class SystemVibrator extends Vibrator {
private final Context mContext;
@GuardedBy("mBrokenListeners")
- private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
+ private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>();
@GuardedBy("mRegisteredListeners")
- private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+ private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener>
mRegisteredListeners = new ArrayMap<>();
private final Object mLock = new Object();
@@ -147,7 +147,7 @@ public class SystemVibrator extends Vibrator {
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
return;
}
- AllVibratorsStateListener delegate = null;
+ MultiVibratorStateListener delegate = null;
try {
synchronized (mRegisteredListeners) {
// If listener is already registered, reject and return.
@@ -155,7 +155,7 @@ public class SystemVibrator extends Vibrator {
Log.w(TAG, "Listener already registered.");
return;
}
- delegate = new AllVibratorsStateListener(executor, listener);
+ delegate = new MultiVibratorStateListener(executor, listener);
delegate.register(mVibratorManager);
mRegisteredListeners.put(listener, delegate);
delegate = null;
@@ -181,7 +181,7 @@ public class SystemVibrator extends Vibrator {
}
synchronized (mRegisteredListeners) {
if (mRegisteredListeners.containsKey(listener)) {
- AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+ MultiVibratorStateListener delegate = mRegisteredListeners.get(listener);
delegate.unregister(mVibratorManager);
mRegisteredListeners.remove(listener);
}
@@ -238,7 +238,7 @@ public class SystemVibrator extends Vibrator {
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
* that were left registered to vibrators after failures to register them to all vibrators.
*
- * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+ * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator
* and also fails to unregister any previously registered single listeners to other vibrators.
*
* <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
@@ -259,10 +259,10 @@ public class SystemVibrator extends Vibrator {
/** Listener for a single vibrator state change. */
private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
- private final AllVibratorsStateListener mAllVibratorsListener;
+ private final MultiVibratorStateListener mAllVibratorsListener;
private final int mVibratorIdx;
- SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+ SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) {
mAllVibratorsListener = listener;
mVibratorIdx = vibratorIdx;
}
@@ -552,8 +552,16 @@ public class SystemVibrator extends Vibrator {
}
}
- /** Listener for all vibrators state change. */
- private static class AllVibratorsStateListener {
+ /**
+ * Listener for all vibrators state change.
+ *
+ * <p>This registers a listener to all vibrators to merge the callbacks into a single state
+ * that is set to true if any individual vibrator is also true, and false otherwise.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class MultiVibratorStateListener {
private final Object mLock = new Object();
private final Executor mExecutor;
private final OnVibratorStateChangedListener mDelegate;
@@ -567,19 +575,21 @@ public class SystemVibrator extends Vibrator {
@GuardedBy("mLock")
private int mVibratingMask;
- AllVibratorsStateListener(@NonNull Executor executor,
+ public MultiVibratorStateListener(@NonNull Executor executor,
@NonNull OnVibratorStateChangedListener listener) {
mExecutor = executor;
mDelegate = listener;
}
- boolean hasRegisteredListeners() {
+ /** Returns true if at least one listener was registered to an individual vibrator. */
+ public boolean hasRegisteredListeners() {
synchronized (mLock) {
return mVibratorListeners.size() > 0;
}
}
- void register(VibratorManager vibratorManager) {
+ /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
+ public void register(VibratorManager vibratorManager) {
int[] vibratorIds = vibratorManager.getVibratorIds();
synchronized (mLock) {
for (int i = 0; i < vibratorIds.length; i++) {
@@ -603,7 +613,8 @@ public class SystemVibrator extends Vibrator {
}
}
- void unregister(VibratorManager vibratorManager) {
+ /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */
+ public void unregister(VibratorManager vibratorManager) {
synchronized (mLock) {
for (int i = mVibratorListeners.size(); --i >= 0; ) {
int vibratorId = mVibratorListeners.keyAt(i);
@@ -614,30 +625,44 @@ public class SystemVibrator extends Vibrator {
}
}
- void onVibrating(int vibratorIdx, boolean vibrating) {
+ /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */
+ public void onVibrating(int vibratorIdx, boolean vibrating) {
mExecutor.execute(() -> {
- boolean anyVibrating;
+ boolean shouldNotifyStateChange;
+ boolean isAnyVibrating;
synchronized (mLock) {
+ // Bitmask indicating that all vibrators have been initialized.
int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
- int vibratorMask = 1 << vibratorIdx;
- if ((mInitializedMask & vibratorMask) == 0) {
- // First state report for this vibrator, set vibrating initial value.
- mInitializedMask |= vibratorMask;
- mVibratingMask |= vibrating ? vibratorMask : 0;
- } else {
- // Flip vibrating value, if changed.
- boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
- if (prevVibrating != vibrating) {
- mVibratingMask ^= vibratorMask;
- }
- }
- if (mInitializedMask != allInitializedMask) {
- // Wait for all vibrators initial state to be reported before delegating.
- return;
+
+ // Save current global state before processing this vibrator state change.
+ boolean previousIsAnyVibrating = (mVibratingMask != 0);
+ boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Mark this vibrator as initialized.
+ int vibratorMask = (1 << vibratorIdx);
+ mInitializedMask |= vibratorMask;
+
+ // Flip the vibrating bit flag for this vibrator, only if the state is changing.
+ boolean previousVibrating = (mVibratingMask & vibratorMask) != 0;
+ if (previousVibrating != vibrating) {
+ mVibratingMask ^= vibratorMask;
}
- anyVibrating = mVibratingMask != 0;
+
+ // Check new global state after processing this vibrator state change.
+ isAnyVibrating = (mVibratingMask != 0);
+ boolean areAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Prevent multiple triggers with the same state.
+ // Trigger once when all vibrators have reported their state, and then only when
+ // the merged vibrating state changes.
+ boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating);
+ shouldNotifyStateChange =
+ areAllInitialized && (!previousAreAllInitialized || isStateChanging);
+ }
+ // Notify delegate listener outside the lock, only if merged state is changing.
+ if (shouldNotifyStateChange) {
+ mDelegate.onVibratorStateChanged(isAnyVibrating);
}
- mDelegate.onVibratorStateChanged(anyVibrating);
});
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d16bbbcaff12..84a61948775c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10897,13 +10897,6 @@ public final class Settings {
"accessibility_floating_menu_migration_tooltip_prompt";
/**
- * Setting that specifies whether the software cursor accessibility service is enabled.
- * @hide
- */
- public static final String ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED =
- "accessibility_software_cursor_enabled";
-
- /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 21c615c26879..7199e57e7970 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -36,6 +36,7 @@ import android.graphics.ColorSpace;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.OverlayProperties;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayManager;
@@ -1290,6 +1291,18 @@ public final class Display {
}
}
+ /** @hide */
+ @Nullable
+ public OverlayProperties getOverlaySupport() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ if (mDisplayInfo.type != TYPE_VIRTUAL) {
+ return mGlobal.getOverlaySupport();
+ }
+ return new OverlayProperties();
+ }
+ }
+
/**
* Gets the supported color modes of this device.
* @hide
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 57ba7e9e816f..fc47c193ec60 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -96,7 +96,8 @@ public class GestureDetector {
* current move {@link MotionEvent}. The distance in x and y is also supplied for
* convenience.
*
- * @param e1 The first down motion event that started the scrolling.
+ * @param e1 The first down motion event that started the scrolling. A {@code null} event
+ * indicates an incomplete event stream or error state.
* @param e2 The move motion event that triggered the current onScroll.
* @param distanceX The distance along the X axis that has been scrolled since the last
* call to onScroll. This is NOT the distance between {@code e1}
@@ -106,7 +107,7 @@ public class GestureDetector {
* and {@code e2}.
* @return true if the event is consumed, else false
*/
- boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
+ boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
float distanceY);
/**
@@ -122,7 +123,8 @@ public class GestureDetector {
* and the matching up {@link MotionEvent}. The calculated velocity is supplied along
* the x and y axis in pixels per second.
*
- * @param e1 The first down motion event that started the fling.
+ * @param e1 The first down motion event that started the fling. A {@code null} event
+ * indicates an incomplete event stream or error state.
* @param e2 The move motion event that triggered the current onFling.
* @param velocityX The velocity of this fling measured in pixels per second
* along the x axis.
@@ -130,7 +132,7 @@ public class GestureDetector {
* along the y axis.
* @return true if the event is consumed, else false
*/
- boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
+ boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
float velocityY);
}
@@ -201,12 +203,12 @@ public class GestureDetector {
public void onLongPress(@NonNull MotionEvent e) {
}
- public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2,
+ public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
- public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
+ public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index d4875d400c5b..fa647d7e9c00 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -17,8 +17,6 @@
package android.view;
import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS;
-import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW;
-import static android.view.ImeFocusControllerProto.SERVED_VIEW;
import android.annotation.AnyThread;
import android.annotation.NonNull;
@@ -28,10 +26,6 @@ import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.InputMethodDebug;
-import com.android.internal.inputmethod.StartInputFlags;
-import com.android.internal.inputmethod.StartInputReason;
-
-import java.util.Objects;
/**
* Responsible for IME focus handling inside {@link ViewRootImpl}.
@@ -43,21 +37,6 @@ public final class ImeFocusController {
private final ViewRootImpl mViewRootImpl;
private boolean mHasImeFocus = false;
-
- /**
- * This is the view that should currently be served by an input method,
- * regardless of the state of setting that up.
- * @see InputMethodManagerDelegate#getLockObject()
- */
- private View mServedView;
-
- /**
- * This is the next view that will be served by the input method, when
- * we get around to updating things.
- * @see InputMethodManagerDelegate#getLockObject()
- */
- private View mNextServedView;
-
private InputMethodManagerDelegate mDelegate;
@UiThread
@@ -86,7 +65,8 @@ public final class ImeFocusController {
@UiThread
void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
- final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+ final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+ windowAttribute.flags);
if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
return;
}
@@ -95,30 +75,20 @@ public final class ImeFocusController {
}
mHasImeFocus = hasImeFocus;
if (mHasImeFocus) {
- onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
- onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
- windowAttribute);
+ getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
+ final View focusedView = mViewRootImpl.mView.findFocus();
+ View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+ getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
}
}
@UiThread
void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
- if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ mHasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(windowAttribute.flags);
+ if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
return;
}
- if (hasWindowFocus) {
- getImmDelegate().setCurrentRootView(mViewRootImpl);
- }
- }
-
- @UiThread
- boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
- final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
- windowAttribute.flags);
- if (force) {
- mHasImeFocus = hasImeFocus;
- }
- return hasImeFocus;
+ getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
}
@UiThread
@@ -134,122 +104,30 @@ public final class ImeFocusController {
windowAttribute.softInputMode));
}
- boolean forceFocus = false;
- final InputMethodManagerDelegate immDelegate = getImmDelegate();
- synchronized (immDelegate.getLockObject()) {
- // Update mNextServedView when focusedView changed.
- onViewFocusChanged(viewForWindowFocus, true);
-
- // Starting new input when the next focused view is same as served view but the
- // currently active connection (if any) is not associated with it.
- final boolean nextFocusIsServedView = mServedView == viewForWindowFocus;
-
- if (nextFocusIsServedView && !immDelegate.hasActiveConnection(viewForWindowFocus)) {
- forceFocus = true;
- }
- }
-
- immDelegate.startInputOnWindowFocusGain(viewForWindowFocus,
- windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
+ getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
}
/**
* @see InputMethodManager#checkFocus()
*/
public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
- final InputMethodManagerDelegate immDelegate = getImmDelegate();
- synchronized (immDelegate.getLockObject()) {
- if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
- 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) {
- immDelegate.finishInput();
- immDelegate.closeCurrentIme();
- return false;
- }
- mServedView = mNextServedView;
- immDelegate.finishComposingText();
- }
-
- if (startInput) {
- immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
- 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
- }
- return true;
+ return getImmDelegate().checkFocus(forceNewFocus, startInput, mViewRootImpl);
}
@UiThread
void onViewFocusChanged(View view, boolean hasFocus) {
- if (view == null || view.isTemporarilyDetached()) {
- return;
- }
- final InputMethodManagerDelegate immDelegate = getImmDelegate();
- synchronized (immDelegate.getLockObject()) {
- if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) {
- return;
- }
- if (!view.hasImeFocus() || !view.hasWindowFocus()) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view)
- + ", mServedView=" + InputMethodDebug.dumpViewInfo(mServedView));
- }
-
- // We don't need to track the next served view when the view lost focus here because:
- // 1) The current view focus may be cleared temporary when in touch mode, closing input
- // at this moment isn't the right way.
- // 2) We only care about the served view change when it focused, since changing input
- // connection when the focus target changed is reasonable.
- // 3) Setting the next served view as null when no more served view should be handled in
- // other special events (e.g. view detached from window or the window dismissed).
- if (hasFocus) {
- mNextServedView = view;
- }
- }
- mViewRootImpl.dispatchCheckFocus();
+ getImmDelegate().onViewFocusChanged(view, hasFocus);
}
@UiThread
void onViewDetachedFromWindow(View view) {
- final InputMethodManagerDelegate immDelegate = getImmDelegate();
- synchronized (immDelegate.getLockObject()) {
- if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) {
- return;
- }
- if (mNextServedView == view) {
- mNextServedView = null;
- }
- if (mServedView == view) {
- mViewRootImpl.dispatchCheckFocus();
- }
- }
+ getImmDelegate().onViewDetachedFromWindow(view, mViewRootImpl);
+
}
@UiThread
void onWindowDismissed() {
- final InputMethodManagerDelegate immDelegate = getImmDelegate();
- synchronized (immDelegate.getLockObject()) {
- if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
- return;
- }
- if (mServedView != null) {
- immDelegate.finishInput();
- }
- immDelegate.setCurrentRootView(null);
- }
+ getImmDelegate().onWindowDismissed(mViewRootImpl);
mHasImeFocus = false;
}
@@ -300,77 +178,16 @@ public final class ImeFocusController {
* @hide
*/
public interface InputMethodManagerDelegate {
- /**
- * Starts the input connection.
- * Note that this method must not hold the {@link InputMethodManager} lock with
- * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager}
- * calling into app-code in different threads.
- */
- boolean startInput(@StartInputReason int startInputReason, View focusedView,
- @StartInputFlags int startInputFlags,
- @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
- /**
- * Starts the input connection when gaining the window focus.
- * Note that this method must not hold the {@link InputMethodManager} lock with
- * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager}
- * calling into app-code in different threads.
- */
- void startInputOnWindowFocusGain(View rootView,
- @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
- boolean forceNewFocus);
- void finishInput();
+ void onPreWindowGainedFocus(ViewRootImpl viewRootImpl);
+ void onPostWindowGainedFocus(View viewForWindowFocus,
+ @NonNull WindowManager.LayoutParams windowAttribute);
+ void onViewFocusChanged(@NonNull View view, boolean hasFocus);
+ boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl);
+ void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl);
+ void onWindowDismissed(ViewRootImpl viewRootImpl);
+
void finishInputAndReportToIme();
- void closeCurrentIme();
- void finishComposingText();
- void setCurrentRootView(ViewRootImpl rootView);
boolean isCurrentRootView(ViewRootImpl rootView);
- boolean hasActiveConnection(View view);
-
- /**
- * Returns the {@code InputMethodManager#mH} lock object.
- * Used for {@link ImeFocusController} to guard the served view being accessed by
- * {@link InputMethodManager} in different threads.
- */
- Object getLockObject();
- }
-
- /**
- * Returns The current IME served view for {@link InputMethodManager}.
- * Used to start input connection or check the caller's validity when calling
- * {@link InputMethodManager} APIs.
- * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for
- * data consistency.
- */
- public View getServedViewLocked() {
- return mServedView;
- }
-
- /**
- * Returns The next incoming IME served view for {@link InputMethodManager}.
- * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for
- * data consistency.
- */
- public View getNextServedViewLocked() {
- return mNextServedView;
- }
-
- /**
- * Clears the served & the next served view when the controller triggers
- * {@link InputMethodManagerDelegate#finishInput()} or
- * {@link InputMethodManagerDelegate#finishInputAndReportToIme()}.
- * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for
- * data consistency.
- *
- * @return The {@code mServedView} that has cleared, or {@code null} means nothing to clear.
- */
- public View clearServedViewsLocked() {
- View clearedView = null;
- mNextServedView = null;
- if (mServedView != null) {
- clearedView = mServedView;
- mServedView = null;
- }
- return clearedView;
}
/**
@@ -384,8 +201,6 @@ public final class ImeFocusController {
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HAS_IME_FOCUS, mHasImeFocus);
- proto.write(SERVED_VIEW, Objects.toString(mServedView));
- proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
proto.end(token);
}
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b5dff5e725fb..ceab310b64b2 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1272,6 +1272,25 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
public static final int AXIS_GENERIC_16 = 47;
+ /**
+ * Axis constant: X gesture offset axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch pad, reports the distance that a swipe gesture has moved in the X axis, as a
+ * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a
+ * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
+ * -0.1.
+ * </ul>
+ */
+ public static final int AXIS_GESTURE_X_OFFSET = 48;
+
+ /**
+ * Axis constant: Y gesture offset axis of a motion event.
+ *
+ * The same as {@link #AXIS_GESTURE_X_OFFSET}, but for the Y axis.
+ */
+ public static final int AXIS_GESTURE_Y_OFFSET = 49;
+
// NOTE: If you add a new axis here you must also add it to:
// frameworks/native/include/android/input.h
// frameworks/native/libs/input/InputEventLabels.cpp
@@ -1325,6 +1344,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
names.append(AXIS_GENERIC_14, "AXIS_GENERIC_14");
names.append(AXIS_GENERIC_15, "AXIS_GENERIC_15");
names.append(AXIS_GENERIC_16, "AXIS_GENERIC_16");
+ names.append(AXIS_GESTURE_X_OFFSET, "AXIS_GESTURE_X_OFFSET");
+ names.append(AXIS_GESTURE_Y_OFFSET, "AXIS_GESTURE_Y_OFFSET");
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b2b5f1356273..58c81260076b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -286,7 +286,7 @@ public final class ViewRootImpl implements ViewParent,
* @hide
*/
public static final boolean LOCAL_LAYOUT =
- SystemProperties.getBoolean("persist.debug.local_layout", false);
+ SystemProperties.getBoolean("persist.debug.local_layout", true);
/**
* Set this system property to true to force the view hierarchy to render
@@ -3798,7 +3798,6 @@ public final class ViewRootImpl implements ViewParent,
}
mAttachInfo.mHasWindowFocus = hasWindowFocus;
- mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */);
mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
if (mView != null) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d0405f0e5991..a59100abadef 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -46,6 +46,7 @@ import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.compat.annotation.ChangeId;
@@ -78,6 +79,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
+import android.util.Pair;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
import android.util.PrintWriterPrinter;
@@ -94,6 +96,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -465,6 +468,22 @@ public final class InputMethodManager {
// -----------------------------------------------------------
/**
+ * This is the view that should currently be served by an input method,
+ * regardless of the state of setting that up.
+ */
+ @Nullable
+ @GuardedBy("mH")
+ private View mServedView;
+
+ /**
+ * This is the next view that will be served by the input method, when
+ * we get around to updating things.
+ */
+ @Nullable
+ @GuardedBy("mH")
+ private View mNextServedView;
+
+ /**
* This is the root view of the overall window that currently has input
* method focus.
*/
@@ -699,8 +718,7 @@ public final class InputMethodManager {
if (mCurRootView == null) {
return null;
}
- final View servedView = mCurRootView.getImeFocusController().getServedViewLocked();
- return servedView != null ? servedView.getContext() : null;
+ return mServedView != null ? mServedView.getContext() : null;
}
}
@@ -721,32 +739,6 @@ public final class InputMethodManager {
private final class DelegateImpl implements
ImeFocusController.InputMethodManagerDelegate {
- /**
- * Used by {@link ImeFocusController} to start input connection.
- */
- @Override
- public boolean startInput(@StartInputReason int startInputReason, View focusedView,
- @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
- int windowFlags) {
- ImeTracing.getInstance().triggerClientDump(
- "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this,
- null /* icProto */);
- return startInputOnWindowFocusGainInternal(startInputReason, focusedView,
- startInputFlags, softInputMode, windowFlags);
- }
-
- /**
- * Used by {@link ImeFocusController} to finish input connection.
- */
- @Override
- public void finishInput() {
- ImeTracing.getInstance().triggerClientDump(
- "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this,
- null /* icProto */);
- synchronized (mH) {
- finishInputLocked();
- }
- }
/**
* Used by {@link ImeFocusController} to finish input connection and callback
@@ -768,19 +760,35 @@ public final class InputMethodManager {
}
}
- /**
- * Used by {@link ImeFocusController} to hide current input method editor.
- */
@Override
- public void closeCurrentIme() {
- closeCurrentInput();
+ public void onPreWindowGainedFocus(ViewRootImpl viewRootImpl) {
+ synchronized (mH) {
+ setCurrentRootViewLocked(viewRootImpl);
+ }
}
- /**
- * For {@link ImeFocusController} to start input when gaining the window focus.
- */
@Override
- public void startInputOnWindowFocusGain(View focusedView,
+ public void onPostWindowGainedFocus(View viewForWindowFocus,
+ @NonNull WindowManager.LayoutParams windowAttribute) {
+ boolean forceFocus = false;
+ synchronized (mH) {
+ // Update mNextServedView when focusedView changed.
+ onViewFocusChangedInternal(viewForWindowFocus, true);
+
+ // Starting new input when the next focused view is same as served view but the
+ // currently active connection (if any) is not associated with it.
+ final boolean nextFocusIsServedView = mServedView == viewForWindowFocus;
+
+ if (nextFocusIsServedView
+ && !hasActiveInputConnectionInternal(viewForWindowFocus)) {
+ 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);
startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
@@ -816,8 +824,7 @@ public final class InputMethodManager {
synchronized (mH) {
// For some reason we didn't do a startInput + windowFocusGain, so
// we'll just do a window focus gain and call it a day.
- View servedView = controller.getServedViewLocked();
- boolean nextFocusHasConnection = servedView != null && servedView == focusedView
+ boolean nextFocusHasConnection = mServedView != null && mServedView == focusedView
&& hasActiveInputConnectionInternal(focusedView);
if (DEBUG) {
Log.v(TAG, "Reporting focus gain, without startInput"
@@ -839,59 +846,93 @@ public final class InputMethodManager {
}
}
- /**
- * Used by {@link ImeFocusController} to finish current composing text.
- */
@Override
- public void finishComposingText() {
+ public void onViewFocusChanged(@Nullable View view, boolean hasFocus) {
+ onViewFocusChangedInternal(view, hasFocus);
+ }
+
+ @Override
+ public boolean checkFocus(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;
+ }
+ mServedView = mNextServedView;
if (mServedInputConnection != null) {
mServedInputConnection.finishComposingTextFromImm();
}
}
+
+ if (startInput) {
+ startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
+ null /* focusedView */,
+ 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
+ }
+ return true;
}
- /**
- * Used for {@link ImeFocusController} to set the current focused root view.
- */
@Override
- public void setCurrentRootView(ViewRootImpl rootView) {
+ public void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl) {
synchronized (mH) {
- mImeDispatcher.switchRootView(mCurRootView, rootView);
- mCurRootView = rootView;
+ if (mCurRootView != view.getViewRootImpl()) {
+ return;
+ }
+ if (mNextServedView == view) {
+ mNextServedView = null;
+ }
+ if (mServedView == view) {
+ viewRootImpl.dispatchCheckFocus();
+ }
}
}
- /**
- * Used for {@link ImeFocusController} to return if the root view from the
- * controller is this {@link InputMethodManager} currently focused.
- * TODO: Address event-order problem when get current root view in multi-threads.
- */
@Override
- public boolean isCurrentRootView(ViewRootImpl rootView) {
+ public void onWindowDismissed(ViewRootImpl viewRootImpl) {
synchronized (mH) {
- return mCurRootView == rootView;
+ if (mCurRootView != viewRootImpl) {
+ return;
+ }
+ if (mServedView != null) {
+ finishInputLocked();
+ }
+ setCurrentRootViewLocked(null);
}
}
- /**
- * Checks whether the active input connection (if any) is for the given view.
- *
- * @see #hasActiveInputConnectionInternal(View)}
- */
- @Override
- public boolean hasActiveConnection(View view) {
- return hasActiveInputConnectionInternal(view);
+ @GuardedBy("mH")
+ private void setCurrentRootViewLocked(ViewRootImpl rootView) {
+ mImeDispatcher.switchRootView(mCurRootView, rootView);
+ mCurRootView = rootView;
}
/**
- * Returns the {@link InputMethodManager#mH} lock object.
- * Used for {@link ImeFocusController} to guard the served view being accessed by
- * {@link InputMethodManager} in different threads.
+ * Used for {@link ImeFocusController} to return if the root view from the
+ * controller is this {@link InputMethodManager} currently focused.
+ * TODO: Address event-order problem when get current root view in multi-threads.
*/
@Override
- public Object getLockObject() {
- return mH;
+ public boolean isCurrentRootView(ViewRootImpl rootView) {
+ synchronized (mH) {
+ return mCurRootView == rootView;
+ }
}
}
@@ -946,14 +987,12 @@ public final class InputMethodManager {
@GuardedBy("mH")
private View getServedViewLocked() {
- return mCurRootView != null ? mCurRootView.getImeFocusController().getServedViewLocked()
- : null;
+ return mCurRootView != null ? mServedView : null;
}
@GuardedBy("mH")
private View getNextServedViewLocked() {
- return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedViewLocked()
- : null;
+ return mCurRootView != null ? mNextServedView : null;
}
private ImeFocusController getFocusController() {
@@ -1802,8 +1841,12 @@ public final class InputMethodManager {
@GuardedBy("mH")
void finishInputLocked() {
mVirtualDisplayToScreenMatrix = null;
- final ImeFocusController controller = getFocusController();
- final View clearedView = controller != null ? controller.clearServedViewsLocked() : null;
+ View clearedView = null;
+ mNextServedView = null;
+ if (mServedView != null) {
+ clearedView = mServedView;
+ mServedView = null;
+ }
if (clearedView != null) {
if (DEBUG) {
Log.v(TAG, "FINISH INPUT: mServedView="
@@ -2325,7 +2368,7 @@ public final class InputMethodManager {
}
/**
- * Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)},
+ * Called when {@link DelegateImpl#checkFocus}, {@link #restartInput(View)},
* {@link #MSG_BIND} or {@link #MSG_UNBIND}.
* 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.
@@ -2384,24 +2427,9 @@ public final class InputMethodManager {
// Okay we are now ready to call into the served view and have it
// do its stuff.
// Life is good: let's hook everything up!
- EditorInfo editorInfo = new EditorInfo();
- // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
- // system can verify the consistency between the uid of this process and package name passed
- // from here. See comment of Context#getOpPackageName() for details.
- editorInfo.packageName = view.getContext().getOpPackageName();
- editorInfo.autofillId = view.getAutofillId();
- editorInfo.fieldId = view.getId();
- InputConnection ic = view.onCreateInputConnection(editorInfo);
- if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
-
- // Clear autofill and field ids if a connection could not be established.
- // This ensures that even disconnected EditorInfos have well-defined attributes,
- // making them consistently and straightforwardly comparable.
- if (ic == null) {
- editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
- editorInfo.fieldId = 0;
- }
-
+ final Pair<InputConnection, EditorInfo> connectionPair = createInputConnection(view);
+ final InputConnection ic = connectionPair.first;
+ final EditorInfo editorInfo = connectionPair.second;
final Handler icHandler;
InputBindResult res = null;
synchronized (mH) {
@@ -2662,6 +2690,40 @@ public final class InputMethodManager {
}
}
+ @UiThread
+ private void onViewFocusChangedInternal(@Nullable View view, boolean hasFocus) {
+ if (view == null || view.isTemporarilyDetached()) {
+ return;
+ }
+ final ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ synchronized (mH) {
+ if (mCurRootView != viewRootImpl) {
+ return;
+ }
+ if (!view.hasImeFocus() || !view.hasWindowFocus()) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onViewFocusChangedInternal, view="
+ + InputMethodDebug.dumpViewInfo(view));
+ }
+
+ // We don't need to track the next served view when the view lost focus here
+ // because:
+ // 1) The current view focus may be cleared temporary when in touch mode, closing
+ // input at this moment isn't the right way.
+ // 2) We only care about the served view change when it focused, since changing
+ // input connection when the focus target changed is reasonable.
+ // 3) Setting the next served view as null when no more served view should be
+ // handled in other special events (e.g. view detached from window or the window
+ // dismissed).
+ if (hasFocus) {
+ mNextServedView = view;
+ }
+ }
+ viewRootImpl.dispatchCheckFocus();
+ }
+
@UnsupportedAppUsage
void closeCurrentInput() {
synchronized (mH) {
@@ -3942,4 +4004,27 @@ public final class InputMethodManager {
consumer.accept(mAccessibilityInputMethodSession.valueAt(i));
}
}
+
+ @UiThread
+ private static Pair<InputConnection, EditorInfo> createInputConnection(
+ @NonNull View servedView) {
+ final EditorInfo editorInfo = new EditorInfo();
+ // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
+ // system can verify the consistency between the uid of this process and package name passed
+ // from here. See comment of Context#getOpPackageName() for details.
+ editorInfo.packageName = servedView.getContext().getOpPackageName();
+ editorInfo.autofillId = servedView.getAutofillId();
+ editorInfo.fieldId = servedView.getId();
+ final InputConnection ic = servedView.onCreateInputConnection(editorInfo);
+ if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
+
+ // Clear autofill and field ids if a connection could not be established.
+ // This ensures that even disconnected EditorInfos have well-defined attributes,
+ // making them consistently and straightforwardly comparable.
+ if (ic == null) {
+ editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
+ editorInfo.fieldId = 0;
+ }
+ return new Pair<>(ic, editorInfo);
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index aa3aefd5b620..ce5365acf1f4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9436,17 +9436,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// The operation should be applied to all characters touched by the line joining the points.
- int startOffset = mLayout.getOffsetForHorizontal(line, startPoint.x);
- int endOffset = mLayout.getOffsetForHorizontal(line, endPoint.x);
- if (startOffset == endOffset) {
+ float lineVerticalCenter = (mLayout.getLineTop(line)
+ + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f;
+ // Create a rectangle which is +/-0.1f around the line's vertical center, so that the
+ // rectangle doesn't touch the line above or below. (The line height is at least 1f.)
+ RectF area = new RectF(
+ Math.min(startPoint.x, endPoint.x),
+ lineVerticalCenter + 0.1f,
+ Math.max(startPoint.x, endPoint.x),
+ lineVerticalCenter - 0.1f);
+ Range<Integer> range = mLayout.getRangeForRect(
+ area, new GraphemeClusterSegmentFinder(mText, mTextPaint),
+ Layout.INCLUSION_STRATEGY_ANY_OVERLAP);
+ if (range == null) {
return handleGestureFailure(gesture);
- } else if (startOffset > endOffset) {
- int tmp = startOffset;
- startOffset = endOffset;
- endOffset = tmp;
}
- // TODO(b/247557062): The boundary offsets might be off by one. We should check which side
- // of the offset the point is on, and adjust if necessary.
+ int startOffset = range.getLower();
+ int endOffset = range.getUpper();
// TODO(b/247557062): This doesn't handle bidirectional text correctly.
Pattern whitespacePattern = getWhitespacePattern();
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1bc8e6d7ef65..8815ab35b671 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -135,8 +135,11 @@ public final class TransitionInfo implements Parcelable {
/** This change happened underneath something else. */
public static final int FLAG_IS_OCCLUDED = 1 << 15;
+ /** The container is a system window, excluding wallpaper and input-method. */
+ public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 16;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 17;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -157,6 +160,7 @@ public final class TransitionInfo implements Parcelable {
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
FLAG_IS_BEHIND_STARTING_WINDOW,
FLAG_IS_OCCLUDED,
+ FLAG_IS_SYSTEM_WINDOW,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -369,6 +373,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_OCCLUDED) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
}
+ if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
@@ -701,14 +708,37 @@ public final class TransitionInfo implements Parcelable {
@Override
public String toString() {
- String out = "{" + mContainer + "(" + mParent + ") leash=" + mLeash
- + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
- + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
- + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation
- + " endFixedRotation=" + mEndFixedRotation;
- if (mSnapshot != null) out += " snapshot=" + mSnapshot;
- if (mLastParent != null) out += " lastParent=" + mLastParent;
- return out + "}";
+ final StringBuilder sb = new StringBuilder();
+ sb.append('{'); sb.append(mContainer);
+ sb.append(" m="); sb.append(modeToString(mMode));
+ sb.append(" f="); sb.append(flagsToString(mFlags));
+ if (mParent != null) {
+ sb.append(" p="); sb.append(mParent);
+ }
+ if (mLeash != null) {
+ sb.append(" leash="); sb.append(mLeash);
+ }
+ sb.append(" sb="); sb.append(mStartAbsBounds);
+ sb.append(" eb="); sb.append(mEndAbsBounds);
+ if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
+ sb.append(" eo="); sb.append(mEndRelOffset);
+ }
+ if (mStartRotation != mEndRotation) {
+ sb.append(" r="); sb.append(mStartRotation);
+ sb.append("->"); sb.append(mEndRotation);
+ sb.append(':'); sb.append(mRotationAnimation);
+ }
+ if (mEndFixedRotation != ROTATION_UNDEFINED) {
+ sb.append(" endFixedRotation="); sb.append(mEndFixedRotation);
+ }
+ if (mSnapshot != null) {
+ sb.append(" snapshot="); sb.append(mSnapshot);
+ }
+ if (mLastParent != null) {
+ sb.append(" lastParent="); sb.append(mLastParent);
+ }
+ sb.append('}');
+ return sb.toString();
}
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 4f74ca72b4aa..2ae2c09680bf 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -43,6 +43,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
@@ -86,6 +87,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
private final ChooserActivityLogger mChooserActivityLogger;
private int mNumShortcutResults = 0;
+ private final Map<SelectableTargetInfo, LoadDirectShareIconTask> mIconLoaders = new HashMap<>();
private boolean mApplySharingAppLimits;
// Reserve spots for incoming direct share targets by adding placeholders
@@ -239,7 +241,6 @@ public class ChooserListAdapter extends ResolverListAdapter {
mListViewDataChanged = false;
}
-
private void createPlaceHolders() {
mNumShortcutResults = 0;
mServiceTargets.clear();
@@ -268,12 +269,16 @@ public class ChooserListAdapter extends ResolverListAdapter {
holder.bindIcon(info);
if (info instanceof SelectableTargetInfo) {
// direct share targets should append the application name for a better readout
- DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
+ SelectableTargetInfo sti = (SelectableTargetInfo) info;
+ DisplayResolveInfo rInfo = sti.getDisplayResolveInfo();
CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
CharSequence extendedInfo = info.getExtendedInfo();
String contentDescription = String.join(" ", info.getDisplayLabel(),
extendedInfo != null ? extendedInfo : "", appName);
holder.updateContentDescription(contentDescription);
+ if (!sti.hasDisplayIcon()) {
+ loadDirectShareIcon(sti);
+ }
} else if (info instanceof DisplayResolveInfo) {
DisplayResolveInfo dri = (DisplayResolveInfo) info;
if (!dri.hasDisplayIcon()) {
@@ -318,6 +323,20 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
}
+ private void loadDirectShareIcon(SelectableTargetInfo info) {
+ LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
+ if (task == null) {
+ task = createLoadDirectShareIconTask(info);
+ mIconLoaders.put(info, task);
+ task.loadIcon();
+ }
+ }
+
+ @VisibleForTesting
+ protected LoadDirectShareIconTask createLoadDirectShareIconTask(SelectableTargetInfo info) {
+ return new LoadDirectShareIconTask(info);
+ }
+
void updateAlphabeticalList() {
new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
@Override
@@ -332,7 +351,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
for (DisplayResolveInfo info : allTargets) {
String resolvedTarget = info.getResolvedComponentName().getPackageName()
- + '#' + info.getDisplayLabel();
+ + '#' + info.getDisplayLabel();
DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
if (multiDri == null) {
consolidated.put(resolvedTarget, info);
@@ -341,7 +360,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
} else {
// create consolidated target from the single DisplayResolveInfo
MultiDisplayResolveInfo multiDisplayResolveInfo =
- new MultiDisplayResolveInfo(resolvedTarget, multiDri);
+ new MultiDisplayResolveInfo(resolvedTarget, multiDri);
multiDisplayResolveInfo.addTarget(info);
consolidated.put(resolvedTarget, multiDisplayResolveInfo);
}
@@ -731,7 +750,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
* Necessary methods to communicate between {@link ChooserListAdapter}
* and {@link ChooserActivity}.
*/
- interface ChooserListCommunicator extends ResolverListCommunicator {
+ @VisibleForTesting
+ public interface ChooserListCommunicator extends ResolverListCommunicator {
int getMaxRankedTargets();
@@ -739,4 +759,35 @@ public class ChooserListAdapter extends ResolverListAdapter {
boolean isSendAction(Intent targetIntent);
}
+
+ /**
+ * Loads direct share targets icons.
+ */
+ @VisibleForTesting
+ public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Boolean> {
+ private final SelectableTargetInfo mTargetInfo;
+
+ private LoadDirectShareIconTask(SelectableTargetInfo targetInfo) {
+ mTargetInfo = targetInfo;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ return mTargetInfo.loadIcon();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean isLoaded) {
+ if (isLoaded) {
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * An alias for execute to use with unit tests.
+ */
+ public void loadIcon() {
+ execute();
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index f6075b008f72..4a1f7eb06c40 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -870,7 +870,12 @@ public class ResolverListAdapter extends BaseAdapter {
void onHandlePackagesChanged(ResolverListAdapter listAdapter);
}
- static class ViewHolder {
+ /**
+ * A view holder keeps a reference to a list view and provides functionality for managing its
+ * state.
+ */
+ @VisibleForTesting
+ public static class ViewHolder {
public View itemView;
public Drawable defaultItemViewBackground;
@@ -878,7 +883,8 @@ public class ResolverListAdapter extends BaseAdapter {
public TextView text2;
public ImageView icon;
- ViewHolder(View view) {
+ @VisibleForTesting
+ public ViewHolder(View view) {
itemView = view;
defaultItemViewBackground = view.getBackground();
text = (TextView) view.findViewById(com.android.internal.R.id.text1);
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 4b9b7cb98dac..d7f3a76c61e0 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -37,6 +37,7 @@ import android.service.chooser.ChooserTarget;
import android.text.SpannableStringBuilder;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ChooserActivity;
import com.android.internal.app.ResolverActivity;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
@@ -59,8 +60,11 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
private final String mDisplayLabel;
private final PackageManager mPm;
private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
+ @GuardedBy("this")
+ private ShortcutInfo mShortcutInfo;
private Drawable mBadgeIcon = null;
private CharSequence mBadgeContentDescription;
+ @GuardedBy("this")
private Drawable mDisplayIcon;
private final Intent mFillInIntent;
private final int mFillInFlags;
@@ -78,6 +82,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
mModifiedScore = modifiedScore;
mPm = mContext.getPackageManager();
mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+ mShortcutInfo = shortcutInfo;
mIsPinned = shortcutInfo != null && shortcutInfo.isPinned();
if (sourceInfo != null) {
final ResolveInfo ri = sourceInfo.getResolveInfo();
@@ -92,8 +97,6 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
}
}
}
- // TODO(b/121287224): do this in the background thread, and only for selected targets
- mDisplayIcon = getChooserTargetIconDrawable(chooserTarget, shortcutInfo);
if (sourceInfo != null) {
mBackupResolveInfo = null;
@@ -118,7 +121,10 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
mChooserTarget = other.mChooserTarget;
mBadgeIcon = other.mBadgeIcon;
mBadgeContentDescription = other.mBadgeContentDescription;
- mDisplayIcon = other.mDisplayIcon;
+ synchronized (other) {
+ mShortcutInfo = other.mShortcutInfo;
+ mDisplayIcon = other.mDisplayIcon;
+ }
mFillInIntent = fillInIntent;
mFillInFlags = flags;
mModifiedScore = other.mModifiedScore;
@@ -141,6 +147,27 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
return mSourceInfo;
}
+ /**
+ * Load display icon, if needed.
+ */
+ public boolean loadIcon() {
+ ShortcutInfo shortcutInfo;
+ Drawable icon;
+ synchronized (this) {
+ shortcutInfo = mShortcutInfo;
+ icon = mDisplayIcon;
+ }
+ boolean shouldLoadIcon = icon == null && shortcutInfo != null;
+ if (shouldLoadIcon) {
+ icon = getChooserTargetIconDrawable(mChooserTarget, shortcutInfo);
+ synchronized (this) {
+ mDisplayIcon = icon;
+ mShortcutInfo = null;
+ }
+ }
+ return shouldLoadIcon;
+ }
+
private Drawable getChooserTargetIconDrawable(ChooserTarget target,
@Nullable ShortcutInfo shortcutInfo) {
Drawable directShareIcon = null;
@@ -271,10 +298,17 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
}
@Override
- public Drawable getDisplayIcon(Context context) {
+ public synchronized Drawable getDisplayIcon(Context context) {
return mDisplayIcon;
}
+ /**
+ * @return true if display icon is available
+ */
+ public synchronized boolean hasDisplayIcon() {
+ return mDisplayIcon != null;
+ }
+
public ChooserTarget getChooserTarget() {
return mChooserTarget;
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 77317d1c3c1a..2433a05a4e9b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2160,6 +2160,11 @@ static jint convertAudioMixToNative(JNIEnv *env,
nCriterion.mValue.mUserId =
env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
break;
+ case RULE_MATCH_AUDIO_SESSION_ID: {
+ jint jAudioSessionId =
+ env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+ nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
+ } break;
case RULE_MATCH_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index acf4da6a5d07..2cd9f8966bb2 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -257,6 +257,13 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr
return INSTALL_FAILED_CONTAINER_ERROR;
}
+ if (fsync(fd) < 0) {
+ ALOGE("Coulnd't fsync temporary file name: %s: %s\n", localTmpFileName, strerror(errno));
+ close(fd);
+ unlink(localTmpFileName);
+ return INSTALL_FAILED_INTERNAL_ERROR;
+ }
+
close(fd);
// Set the modification time for this file to the ZIP's mod time.
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 322354b5ad60..285258a979e4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -88,7 +88,6 @@ message SecureSettingsProto {
optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f0b1b2aa7e3d..dbfefd0f89d5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1040,25 +1040,38 @@
android:priority="900" />
<!-- Allows an application to read from external storage.
- <p>Any app that declares the {@link #WRITE_EXTERNAL_STORAGE} permission is implicitly
- granted this permission.</p>
+ <p class="note"><strong>Note: </strong>Starting in API level 33, this permission has no
+ effect. If your app accesses other apps' media files, request one or more of these permissions
+ instead: <a href="#READ_MEDIA_IMAGES"><code>READ_MEDIA_IMAGES</code></a>,
+ <a href="#READ_MEDIA_VIDEO"><code>READ_MEDIA_VIDEO</code></a>,
+ <a href="#READ_MEDIA_AUDIO"><code>READ_MEDIA_AUDIO</code></a>. Learn more about the
+ <a href="{@docRoot}training/data-storage/shared/media#storage-permission">storage
+ permissions</a> that are associated with media files.</p>
+
<p>This permission is enforced starting in API level 19. Before API level 19, this
permission is not enforced and all apps still have access to read from external storage.
You can test your app with the permission enforced by enabling <em>Protect USB
- storage</em> under Developer options in the Settings app on a device running Android 4.1 or
- higher.</p>
+ storage</em> under <b>Developer options</b> in the Settings app on a device running Android
+ 4.1 or higher.</p>
<p>Also starting in API level 19, this permission is <em>not</em> required to
- read/write files in your application-specific directories returned by
+ read or write files in your application-specific directories returned by
{@link android.content.Context#getExternalFilesDir} and
- {@link android.content.Context#getExternalCacheDir}.
- <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ {@link android.content.Context#getExternalCacheDir}.</p>
+ <p>Starting in API level 29, apps don't need to request this permission to access files in
+ their app-specific directory on external storage, or their own files in the
+ <a href="{@docRoot}reference/android/provider/MediaStore"><code>MediaStore</code></a>. Apps
+ shouldn't request this permission unless they need to access other apps' files in the
+ <code>MediaStore</code>. Read more about these changes in the
+ <a href="{@docRoot}training/data-storage#scoped-storage">scoped storage</a> section of the
+ developer documentation.</p>
+ <p>If <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
minSdkVersion}</a> and <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
grants your app this permission. If you don't need this permission, be sure your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
- targetSdkVersion}</a> is 4 or higher.
+ targetSdkVersion}</a> is 4 or higher.</p>
<p> This is a soft restricted permission which cannot be held by an app it its
full form until the installer on record allowlists the permission.
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
new file mode 100644
index 000000000000..57b9cb1a9097
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.annotation.Nullable;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import org.junit.Test;
+
+public final class ProgramSelectorTest {
+
+ private static final int FM_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_FM;
+ private static final int DAB_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_DAB;
+ private static final long FM_FREQUENCY = 88500;
+ private static final long AM_FREQUENCY = 700;
+ private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_1 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x1000011);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_2 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x10000112);
+ private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 94500);
+
+ @Test
+ public void getType_forIdentifier() {
+ assertWithMessage("Identifier type").that(FM_IDENTIFIER.getType())
+ .isEqualTo(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+ }
+
+ @Test
+ public void isCategoryType_withCategoryTypeForIdentifier() {
+ int typeChecked = ProgramSelector.IDENTIFIER_TYPE_VENDOR_START + 1;
+ ProgramSelector.Identifier fmIdentifier = new ProgramSelector.Identifier(
+ typeChecked, /* value= */ 99901);
+
+ assertWithMessage("Whether %s is a category identifier type", typeChecked)
+ .that(fmIdentifier.isCategoryType()).isTrue();
+ }
+
+ @Test
+ public void isCategoryType_withNonCategoryTypeForIdentifier() {
+ assertWithMessage("Is AMFM_FREQUENCY category identifier type")
+ .that(FM_IDENTIFIER.isCategoryType()).isFalse();
+ }
+
+ @Test
+ public void getValue_forIdentifier() {
+ assertWithMessage("Identifier value")
+ .that(FM_IDENTIFIER.getValue()).isEqualTo(FM_FREQUENCY);
+ }
+
+ @Test
+ public void equals_withDifferentTypesForIdentifiers_returnsFalse() {
+ assertWithMessage("Identifier with different identifier type")
+ .that(FM_IDENTIFIER).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ }
+
+ @Test
+ public void equals_withDifferentValuesForIdentifiers_returnsFalse() {
+ assertWithMessage("Identifier with different identifier value")
+ .that(DAB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ }
+
+ @Test
+ public void equals_withSameKeyAndValueForIdentifiers_returnsTrue() {
+ ProgramSelector.Identifier fmIdentifierSame = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+
+ assertWithMessage("Identifier of the same identifier")
+ .that(FM_IDENTIFIER).isEqualTo(fmIdentifierSame);
+ }
+
+ @Test
+ public void getProgramType() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ int programType = selector.getProgramType();
+
+ assertWithMessage("Program type").that(programType).isEqualTo(FM_PROGRAM_TYPE);
+ }
+
+ @Test
+ public void getPrimaryId() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier programId = selector.getPrimaryId();
+
+ assertWithMessage("Program Id").that(programId).isEqualTo(FM_IDENTIFIER);
+ }
+
+ @Test
+ public void getSecondaryIds_withEmptySecondaryIds() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] secondaryIds = selector.getSecondaryIds();
+
+ assertWithMessage("Secondary ids of selector initialized with empty secondary ids")
+ .that(secondaryIds).isEmpty();
+ }
+
+ @Test
+ public void getSecondaryIds_withNonEmptySecondaryIds() {
+ ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIdsExpected, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] secondaryIds = selector.getSecondaryIds();
+
+ assertWithMessage("Secondary identifier got")
+ .that(secondaryIds).isEqualTo(secondaryIdsExpected);
+ }
+
+ @Test
+ public void getFirstId_withIdInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+
+ assertWithMessage("Value of the first DAB_SID_EXT identifier")
+ .that(firstIdValue).isEqualTo(DAB_SID_EXT_IDENTIFIER_1.getValue());
+ }
+
+ @Test
+ public void getFirstId_withIdNotInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ int idType = ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY;
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ selector.getFirstId(idType);
+ });
+
+ assertWithMessage("Exception for getting first identifier %s", idType)
+ .that(thrown).hasMessageThat().contains("Identifier " + idType + " not found");
+ }
+
+ @Test
+ public void getAllIds_withIdInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector.Identifier[] allIdsExpected =
+ {DAB_SID_EXT_IDENTIFIER_1, DAB_SID_EXT_IDENTIFIER_2};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] allIds =
+ selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+
+ assertWithMessage("All DAB_SID_EXT identifiers in selector")
+ .that(allIds).isEqualTo(allIdsExpected);
+ }
+
+ @Test
+ public void getAllIds_withIdNotInSelector() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
+
+ ProgramSelector.Identifier[] allIds =
+ selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+
+ assertWithMessage("AMFM frequency identifiers found in selector")
+ .that(allIds).isEmpty();
+ }
+
+ @Test
+ public void getVendorIds_withEmptyVendorIds() {
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+
+ long[] vendorIds = selector.getVendorIds();
+
+ assertWithMessage("Vendor Ids of selector initialized with empty vendor ids")
+ .that(vendorIds).isEmpty();
+ }
+
+ @Test
+ public void getVendorIds_withNonEmptyVendorIds() {
+ long[] vendorIdsExpected = {12345, 678};
+ ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, vendorIdsExpected);
+
+ long[] vendorIds = selector.getVendorIds();
+
+ assertWithMessage("Vendor Ids of selector initialized with non-empty vendor ids")
+ .that(vendorIds).isEqualTo(vendorIdsExpected);
+ }
+
+ @Test
+ public void withSecondaryPreferred() {
+ ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ long[] vendorIdsExpected = {12345, 678};
+ ProgramSelector selector = getDabSelector(secondaryIds, vendorIdsExpected);
+ ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_1};
+
+ ProgramSelector selectorPreferred =
+ selector.withSecondaryPreferred(DAB_SID_EXT_IDENTIFIER_1);
+
+ assertWithMessage("Program type")
+ .that(selectorPreferred.getProgramType()).isEqualTo(selector.getProgramType());
+ assertWithMessage("Primary identifiers")
+ .that(selectorPreferred.getPrimaryId()).isEqualTo(selector.getPrimaryId());
+ assertWithMessage("Secondary identifiers")
+ .that(selectorPreferred.getSecondaryIds()).isEqualTo(secondaryIdsExpected);
+ assertWithMessage("Vendor Ids")
+ .that(selectorPreferred.getVendorIds()).isEqualTo(vendorIdsExpected);
+ }
+
+ @Test
+ public void createAmFmSelector_withValidFrequencyWithoutSubChannel() {
+ int band = RadioManager.BAND_AM;
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void createAmFmSelector_withoutBandAndSubChannel() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(
+ RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void createAmFmSelector_withValidFrequencyAndSubChannel() {
+ int band = RadioManager.BAND_AM_HD;
+ int subChannel = 2;
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+ ProgramSelector.Identifier[] secondaryIdExpected = {
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)
+ };
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
+ subChannel);
+
+ assertWithMessage("Program type")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("Primary identifiers")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ assertWithMessage("Secondary identifiers")
+ .that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
+ }
+
+ @Test
+ public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
+ int invalidFrequency = 50000;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_AM, invalidFrequency);
+ });
+
+ assertWithMessage("Exception for using invalid frequency %s", invalidFrequency)
+ .that(thrown).hasMessageThat().contains(
+ "Provided value is not a valid AM/FM frequency: " + invalidFrequency);
+ }
+
+ @Test
+ public void createAmFmSelector_withInvalidSubChannel_throwsIllegalArgumentException() {
+ int invalidSubChannel = 9;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, (int) FM_FREQUENCY,
+ invalidSubChannel);
+ });
+
+ assertWithMessage("Exception for using invalid subchannel %s", invalidSubChannel)
+ .that(thrown).hasMessageThat().contains(
+ "Invalid subchannel: " + invalidSubChannel);
+ }
+
+ @Test
+ public void createAmFmSelector_withSubChannelNotSupported_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, (int) FM_FREQUENCY,
+ /* subChannel= */ 1);
+ });
+
+ assertWithMessage("Exception for using sub-channel on radio not supporting it")
+ .that(thrown)
+ .hasMessageThat().contains("Subchannels are not supported for non-HD radio");
+ }
+
+ @Test
+ public void equals_withDifferentSecondaryIds_returnTrue() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector selector2 = getDabSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage("Selector with different secondary id")
+ .that(selector1).isEqualTo(selector2);
+ }
+
+ @Test
+ public void equals_withDifferentPrimaryIds_returnFalse() {
+ ProgramSelector selector1 = getFmSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ ProgramSelector.Identifier fmIdentifier2 = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88700);
+ ProgramSelector selector2 = new ProgramSelector(FM_PROGRAM_TYPE, fmIdentifier2,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage("Selector with different primary id")
+ .that(selector1).isNotEqualTo(selector2);
+ }
+
+ @Test
+ public void strictEquals_withDifferentPrimaryIds_returnsFalse() {
+ ProgramSelector selector1 = getFmSelector(
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ ProgramSelector.Identifier fmIdentifier2 = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88700);
+ ProgramSelector selector2 = new ProgramSelector(FM_PROGRAM_TYPE, fmIdentifier2,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different primary ids are strictly equal")
+ .that(selector1.strictEquals(selector2)).isFalse();
+ }
+
+ @Test
+ public void strictEquals_withDifferentSecondaryIds_returnsFalse() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector.Identifier[] secondaryIds2 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER};
+ ProgramSelector selector2 = getDabSelector(secondaryIds2, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different secondary ids are strictly equal")
+ .that(selector1.strictEquals(selector2)).isFalse();
+ }
+
+ @Test
+ public void strictEquals_withDifferentSecondaryIdsOrders_returnsTrue() {
+ ProgramSelector.Identifier[] secondaryIds1 = new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER};
+ ProgramSelector selector1 = getDabSelector(secondaryIds1, /* vendorIds= */ null);
+ ProgramSelector.Identifier[] secondaryIds2 = new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER};
+ ProgramSelector selector2 = getDabSelector(secondaryIds2, /* vendorIds= */ null);
+
+ assertWithMessage(
+ "Whether two selectors with different secondary id orders are strictly equal")
+ .that(selector1.strictEquals(selector2)).isTrue();
+ }
+
+ private ProgramSelector getFmSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
+ @Nullable long[] vendorIds) {
+ return new ProgramSelector(FM_PROGRAM_TYPE, FM_IDENTIFIER, secondaryIds, vendorIds);
+ }
+
+ private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
+ @Nullable long[] vendorIds) {
+ return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_SID_EXT_IDENTIFIER_1, secondaryIds,
+ vendorIds);
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
new file mode 100644
index 000000000000..7f71921ed047
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -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.server.broadcastradio.aidl;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests for AIDL HAL RadioModule.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class RadioModuleTest {
+
+ // Mocks
+ @Mock
+ private IBroadcastRadio mBroadcastRadioMock;
+
+ private final Object mLock = new Object();
+ // RadioModule under test
+ private RadioModule mRadioModule;
+
+ @Before
+ public void setup() throws RemoteException {
+ mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
+ /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+ /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+ /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
+ /* supportedProgramTypes= */ new int[]{},
+ /* supportedIdentifierTypes */ new int[]{},
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+
+ // TODO(b/241118988): test non-null image for getImage method
+ when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
+ }
+
+ @Test
+ public void getService() {
+ assertWithMessage("Service of radio module")
+ .that(mRadioModule.getService()).isEqualTo(mBroadcastRadioMock);
+ }
+
+ @Test
+ public void setInternalHalCallback_callbackSetInHal() throws RemoteException {
+ mRadioModule.setInternalHalCallback();
+
+ verify(mBroadcastRadioMock).setTunerCallback(any());
+ }
+
+ @Test
+ public void getImage_withValidIdFromRadioModule() {
+ int imageId = 1;
+
+ Bitmap imageTest = mRadioModule.getImage(imageId);
+
+ assertWithMessage("Image got from radio module").that(imageTest).isNull();
+ }
+
+ @Test
+ public void getImage_withInvalidIdFromRadioModule_throwsIllegalArgumentException() {
+ int invalidImageId = IBroadcastRadio.INVALID_IMAGE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mRadioModule.getImage(invalidImageId);
+ });
+
+ assertWithMessage("Exception for getting image with invalid ID")
+ .that(thrown).hasMessageThat().contains("Image ID is missing");
+ }
+}
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index daae9579cabe..180a312a3f2b 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -36,7 +36,6 @@ import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.StrictMode;
-import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -48,6 +47,9 @@ import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -57,11 +59,22 @@ import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
/**
* Tests for BugreportManager API.
@@ -74,6 +87,7 @@ public class BugreportManagerTest {
private static final String TAG = "BugreportManagerTest";
private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final long DUMPSTATE_TEARDOWN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
@@ -89,6 +103,18 @@ public class BugreportManagerTest {
private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+ private static final Path[] UI_TRACES_PREDUMPED = {
+ Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
+ Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
+ Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
+ Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
+ };
+ private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
+ Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
+ };
+
private Handler mHandler;
private Executor mExecutor;
private BugreportManager mBrm;
@@ -124,7 +150,6 @@ public class BugreportManagerTest {
FileUtils.closeQuietly(mScreenshotFd);
}
-
@Test
public void normalFlow_wifi() throws Exception {
BugreportCallbackImpl callback = new BugreportCallbackImpl();
@@ -175,6 +200,66 @@ public class BugreportManagerTest {
assertFdsAreClosed(mBugreportFd, mScreenshotFd);
}
+ @LargeTest
+ @Test
+ public void preDumpUiData_then_fullWithUsePreDumpFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+ List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+ List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
+ }
+
+ @LargeTest
+ @Test
+ public void preDumpData_then_fullWithoutUsePreDumpFlag_ignoresPreDump() throws Exception {
+ startPreDumpedUiTraces();
+
+ // Simulate pre-dump, instead of taking a real one.
+ // In some corner cases, data dumped as part of the full bugreport could be the same as the
+ // pre-dumped data and this test would fail. Hence, here we create fake/artificial
+ // pre-dumped data that we know it won't match with the full bugreport data.
+ createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system");
+
+ List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+ List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+ assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -384,21 +469,151 @@ public class BugreportManagerTest {
}
return bm;
}
-
private static File createTempFile(String prefix, String extension) throws Exception {
final File f = File.createTempFile(prefix, extension);
f.setReadable(true, true);
f.setWritable(true, true);
-
f.deleteOnExit();
return f;
}
+ private static void startPreDumpedUiTraces() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd input_method tracing start"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd window tracing start"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "service call SurfaceFlinger 1025 i32 1"
+ );
+ }
+
+ private static void stopPreDumpedUiTraces() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd input_method tracing stop"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cmd window tracing stop"
+ );
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "service call SurfaceFlinger 1025 i32 0"
+ );
+ }
+
+ private void assertThatBugreportContainsFiles(Path[] paths)
+ throws IOException {
+ List<Path> entries = listZipArchiveEntries(mBugreportFile);
+ for (Path pathInDevice : paths) {
+ Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+ assertThat(entries).contains(pathInArchive);
+ }
+ }
+
+ private List<File> extractFilesFromBugreport(Path[] paths) throws Exception {
+ List<File> files = new ArrayList<File>();
+ for (Path pathInDevice : paths) {
+ Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+ files.add(extractZipArchiveEntry(mBugreportFile, pathInArchive));
+ }
+ return files;
+ }
+
+ private static List<Path> listZipArchiveEntries(File archive) throws IOException {
+ ArrayList<Path> entries = new ArrayList<>();
+
+ ZipInputStream stream = new ZipInputStream(
+ new BufferedInputStream(new FileInputStream(archive)));
+
+ for (ZipEntry entry = stream.getNextEntry(); entry != null; entry = stream.getNextEntry()) {
+ entries.add(Paths.get(entry.toString()));
+ }
+
+ return entries;
+ }
+
+ private static File extractZipArchiveEntry(File archive, Path entryToExtract)
+ throws Exception {
+ File extractedFile = createTempFile(entryToExtract.getFileName().toString(), ".extracted");
+
+ ZipInputStream is = new ZipInputStream(new FileInputStream(archive));
+ boolean hasFoundEntry = false;
+
+ for (ZipEntry entry = is.getNextEntry(); entry != null; entry = is.getNextEntry()) {
+ if (entry.toString().equals(entryToExtract.toString())) {
+ BufferedOutputStream os =
+ new BufferedOutputStream(new FileOutputStream(extractedFile));
+ ByteStreams.copy(is, os);
+ os.close();
+ hasFoundEntry = true;
+ break;
+ }
+
+ ByteStreams.exhaust(is); // skip entry
+ }
+
+ is.closeEntry();
+ is.close();
+
+ assertThat(hasFoundEntry).isTrue();
+
+ return extractedFile;
+ }
+
+ private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception {
+ File src = createTempFile("fake", ".data");
+ Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
+
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "install -m 611 -o " + owner + " -g " + owner
+ + " " + src.getAbsolutePath() + " " + path.toString()
+ );
+ }
+ }
+
+ private static List<File> copyFilesAsRoot(Path[] paths) throws Exception {
+ ArrayList<File> files = new ArrayList<File>();
+ for (Path src : paths) {
+ File dst = createTempFile(src.getFileName().toString(), ".copy");
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "cp " + src.toString() + " " + dst.getAbsolutePath()
+ );
+ files.add(dst);
+ }
+ return files;
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
}
+ private static void assertThatAllFileContentsAreEqual(List<File> actual, List<File> expected)
+ throws IOException {
+ if (actual.size() != expected.size()) {
+ fail("File lists have different size");
+ }
+ for (int i = 0; i < actual.size(); ++i) {
+ if (!Files.equal(actual.get(i), expected.get(i))) {
+ fail("Contents of " + actual.get(i).toString()
+ + " != " + expected.get(i).toString());
+ }
+ }
+ }
+
+ private static void assertThatAllFileContentsAreDifferent(List<File> a, List<File> b)
+ throws IOException {
+ if (a.size() != b.size()) {
+ fail("File lists have different size");
+ }
+ for (int i = 0; i < a.size(); ++i) {
+ if (Files.equal(a.get(i), b.get(i))) {
+ fail("Contents of " + a.get(i).toString() + " == " + b.get(i).toString());
+ }
+ }
+ }
+
private static void dropPermissions() {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
@@ -410,21 +625,16 @@ public class BugreportManagerTest {
}
private static boolean isDumpstateRunning() {
- String[] output;
+ String output;
try {
- output =
- UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- .executeShellCommand("ps -A -o NAME | grep dumpstate")
- .trim()
- .split("\n");
+ output = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand("service list | grep dumpstate");
} catch (IOException e) {
Log.w(TAG, "Failed to check if dumpstate is running", e);
return false;
}
- for (String line : output) {
- // Check for an exact match since there may be other things that contain "dumpstate" as
- // a substring (e.g. the dumpstate HAL).
- if (TextUtils.equals("dumpstate", line)) {
+ for (String line : output.trim().split("\n")) {
+ if (line.matches("^.*\\s+dumpstate:\\s+\\[.*\\]$")) {
return true;
}
}
@@ -449,6 +659,17 @@ public class BugreportManagerTest {
return System.currentTimeMillis();
}
+ private static void waitTillDumpstateExitedOrTimeout() throws Exception {
+ long startTimeMs = now();
+ while (isDumpstateRunning()) {
+ Thread.sleep(500 /* .5s */);
+ if (now() - startTimeMs >= DUMPSTATE_TEARDOWN_TIMEOUT_MS) {
+ break;
+ }
+ Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for dumpstate to exit");
+ }
+ }
+
private static void waitTillDumpstateRunningOrTimeout() throws Exception {
long startTimeMs = now();
while (!isDumpstateRunning()) {
@@ -500,6 +721,16 @@ public class BugreportManagerTest {
return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
}
+ /*
+ * Returns a {@link BugreportParams} for full bugreport that reuses pre-dumped data.
+ *
+ * <p> This can take on the order of minutes to finish
+ */
+ private static BugreportParams fullWithUsePreDumpFlag() {
+ return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ }
+
/* Allow/deny the consent dialog to sharing bugreport data or check existence only. */
private enum ConsentReply {
ALLOW,
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index f9f3b4c8ead1..0b8b29b9dda9 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -59,6 +59,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import android.annotation.Nullable;
+import android.app.Notification.CallStyle;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -92,6 +93,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@@ -531,6 +533,108 @@ public class NotificationTest {
}
@Test
+ public void testCallStyle_getSystemActions_forIncomingCall() {
+ PendingIntent answerIntent = createPendingIntent("answer");
+ PendingIntent declineIntent = createPendingIntent("decline");
+ Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("A Caller").build(),
+ declineIntent,
+ answerIntent
+ );
+ style.setBuilder(new Notification.Builder(mContext, "Channel"));
+
+ List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+ assertEquals(2, actions.size());
+ assertEquals(declineIntent, actions.get(0).actionIntent);
+ assertEquals(answerIntent, actions.get(1).actionIntent);
+ }
+
+ @Test
+ public void testCallStyle_getSystemActions_forOngoingCall() {
+ PendingIntent hangUpIntent = createPendingIntent("hangUp");
+ Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+ new Person.Builder().setName("A Caller").build(),
+ hangUpIntent
+ );
+ style.setBuilder(new Notification.Builder(mContext, "Channel"));
+
+ List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+ assertEquals(1, actions.size());
+ assertEquals(hangUpIntent, actions.get(0).actionIntent);
+ }
+
+ @Test
+ public void testCallStyle_getSystemActions_forIncomingCallWithOtherActions() {
+ PendingIntent answerIntent = createPendingIntent("answer");
+ PendingIntent declineIntent = createPendingIntent("decline");
+ Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("A Caller").build(),
+ declineIntent,
+ answerIntent
+ );
+ Notification.Action actionToKeep = makeNotificationAction(null);
+ Notification.Action actionToDrop = makeNotificationAction(null);
+ Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+ .addAction(actionToKeep)
+ .addAction(actionToDrop); //expect to move this action to the end
+ style.setBuilder(notifBuilder); //add a builder with actions
+
+ List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+ assertEquals(4, actions.size());
+ assertEquals(declineIntent, actions.get(0).actionIntent);
+ assertEquals(actionToKeep, actions.get(1));
+ assertEquals(answerIntent, actions.get(2).actionIntent);
+ assertEquals(actionToDrop, actions.get(3));
+ }
+
+ @Test
+ public void testCallStyle_getSystemActions_forOngoingCallWithOtherActions() {
+ PendingIntent hangUpIntent = createPendingIntent("hangUp");
+ Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+ new Person.Builder().setName("A Caller").build(),
+ hangUpIntent
+ );
+ Notification.Action firstAction = makeNotificationAction(null);
+ Notification.Action secondAction = makeNotificationAction(null);
+ Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+ .addAction(firstAction)
+ .addAction(secondAction);
+ style.setBuilder(notifBuilder); //add a builder with actions
+
+ List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+ assertEquals(3, actions.size());
+ assertEquals(hangUpIntent, actions.get(0).actionIntent);
+ assertEquals(firstAction, actions.get(1));
+ assertEquals(secondAction, actions.get(2));
+ }
+
+ @Test
+ public void testCallStyle_getSystemActions_dropsOldSystemActions() {
+ PendingIntent hangUpIntent = createPendingIntent("decline");
+ Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+ new Person.Builder().setName("A Caller").build(),
+ hangUpIntent
+ );
+ Bundle actionExtras = new Bundle();
+ actionExtras.putBoolean("key_action_priority", true);
+ Notification.Action oldSystemAction = makeNotificationAction(
+ builder -> builder.addExtras(actionExtras)
+ );
+ Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+ .addAction(oldSystemAction);
+ style.setBuilder(notifBuilder); //add a builder with actions
+
+ List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+ assertFalse("Old versions of system actions should be dropped.",
+ actions.contains(oldSystemAction));
+ }
+
+ @Test
public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() {
Icon hugeIcon = Icon.createWithBitmap(
Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
@@ -788,7 +892,7 @@ public class NotificationTest {
@Test
public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
- Notification.Style style = new Notification.CallStyle();
+ Notification.Style style = new CallStyle();
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
@@ -962,4 +1066,12 @@ public class NotificationTest {
}
return actionBuilder.build();
}
+
+ /**
+ * Creates a PendingIntent with the given action.
+ */
+ private PendingIntent createPendingIntent(String action) {
+ return PendingIntent.getActivity(mContext, 0, new Intent(action),
+ PendingIntent.FLAG_MUTABLE);
+ }
}
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7a66befad1a1..7ebebc965d8d 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -21,12 +21,17 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+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.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -34,6 +39,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -46,6 +52,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.junit.MockitoJUnitRunner;
/**
@@ -65,6 +72,7 @@ public class VibratorTest {
private Context mContextSpy;
private Vibrator mVibratorSpy;
+ private TestLooper mTestLooper;
@Before
public void setUp() {
@@ -73,6 +81,7 @@ public class VibratorTest {
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
mVibratorSpy = spy(new SystemVibrator(mContextSpy));
+ mTestLooper = new TestLooper();
}
@Test
@@ -395,6 +404,108 @@ public class VibratorTest {
}
@Test
+ public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+
+ // Never tries to register a listener to an individual vibrator.
+ assertFalse(multiVibratorListener.hasRegisteredListeners());
+ verify(mockVibratorManager, never()).getVibrator(anyInt());
+ }
+
+ @Test
+ public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true);
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+
+ mTestLooper.dispatchAll();
+
+ InOrder inOrder = inOrder(mockListener);
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(true));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
+ mTestLooper.dispatchAll();
+ verify(mockListener, never()).onVibratorStateChanged(anyBoolean());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false);
+ mTestLooper.dispatchAll();
+ verify(mockListener).onVibratorStateChanged(eq(false));
+ verifyNoMoreInteractions(mockListener);
+ }
+
+ @Test
+ public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() {
+ VibratorManager mockVibratorManager = mock(VibratorManager.class);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
+
+ Vibrator.OnVibratorStateChangedListener mockListener =
+ mock(Vibrator.OnVibratorStateChangedListener.class);
+ SystemVibrator.MultiVibratorStateListener multiVibratorListener =
+ new SystemVibrator.MultiVibratorStateListener(
+ mTestLooper.getNewExecutor(), mockListener);
+
+ multiVibratorListener.register(mockVibratorManager);
+ assertTrue(multiVibratorListener.hasRegisteredListeners());
+
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ true); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // true
+ multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false
+
+ mTestLooper.dispatchAll();
+
+ InOrder inOrder = inOrder(mockListener);
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(true));
+ inOrder.verify(mockListener).onVibratorStateChanged(eq(false));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
new file mode 100644
index 000000000000..8218b9869b5d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -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 com.android.internal.app
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.Bundle
+import android.service.chooser.ChooserTarget
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.R
+import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask
+import com.android.internal.app.chooser.SelectableTargetInfo
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
+import com.android.internal.app.chooser.TargetInfo
+import com.android.server.testutils.any
+import com.android.server.testutils.mock
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class ChooserListAdapterTest {
+ private val packageManager = mock<PackageManager> {
+ whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
+ }
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ private val resolverListController = mock<ResolverListController>()
+ private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
+ whenever(maxRankedTargets).thenReturn(0)
+ }
+ private val selectableTargetInfoCommunicator =
+ mock<SelectableTargetInfoCommunicator> {
+ whenever(targetIntent).thenReturn(mock())
+ }
+ private val chooserActivityLogger = mock<ChooserActivityLogger>()
+
+ private fun createChooserListAdapter(
+ taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+ ) =
+ ChooserListAdapterOverride(
+ context,
+ emptyList(),
+ emptyArray(),
+ emptyList(),
+ false,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger,
+ taskProvider
+ )
+
+ @Test
+ fun testDirectShareTargetLoadingIconIsStarted() {
+ val view = createView()
+ val viewHolder = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolder
+ val targetInfo = createSelectableTargetInfo()
+ val iconTask = mock<LoadDirectShareIconTask>()
+ val testSubject = createChooserListAdapter { iconTask }
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTask, times(1)).loadIcon()
+ }
+
+ @Test
+ fun testOnlyOneTaskPerTarget() {
+ val view = createView()
+ val viewHolderOne = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderOne
+ val targetInfo = createSelectableTargetInfo()
+ val iconTaskOne = mock<LoadDirectShareIconTask>()
+ val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
+ whenever(invoke()).thenReturn(iconTaskOne)
+ }
+ val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderTwo
+ whenever(testTaskProvider()).thenReturn(mock())
+
+ testSubject.testViewBind(view, targetInfo, 0)
+
+ verify(iconTaskOne, times(1)).loadIcon()
+ verify(testTaskProvider, times(1)).invoke()
+ }
+
+ private fun createSelectableTargetInfo(): SelectableTargetInfo =
+ SelectableTargetInfo(
+ context,
+ null,
+ createChooserTarget(),
+ 1f,
+ selectableTargetInfoCommunicator,
+ null
+ )
+
+ private fun createChooserTarget(): ChooserTarget =
+ ChooserTarget(
+ "Title",
+ null,
+ 1f,
+ ComponentName("package", "package.Class"),
+ Bundle()
+ )
+
+ private fun createView(): View {
+ val view = FrameLayout(context)
+ TextView(context).apply {
+ id = R.id.text1
+ view.addView(this)
+ }
+ TextView(context).apply {
+ id = R.id.text2
+ view.addView(this)
+ }
+ ImageView(context).apply {
+ id = R.id.icon
+ view.addView(this)
+ }
+ return view
+ }
+}
+
+private class ChooserListAdapterOverride(
+ context: Context?,
+ payloadIntents: List<Intent>?,
+ initialIntents: Array<out Intent>?,
+ rList: List<ResolveInfo>?,
+ filterLastUsed: Boolean,
+ resolverListController: ResolverListController?,
+ chooserListCommunicator: ChooserListCommunicator?,
+ selectableTargetInfoCommunicator: SelectableTargetInfoCommunicator?,
+ packageManager: PackageManager?,
+ chooserActivityLogger: ChooserActivityLogger?,
+ private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+) : ChooserListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger
+) {
+ override fun createLoadDirectShareIconTask(
+ info: SelectableTargetInfo?
+ ): LoadDirectShareIconTask =
+ taskProvider.invoke(info)
+
+ fun testViewBind(view: View?, info: TargetInfo?, position: Int) {
+ onBindView(view, info, position)
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8ca16071a1e4..699e7947410e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,8 @@ applications that come with the platform
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.ACCESS_LOWPAN_STATE"/>
<permission name="android.permission.BACKUP"/>
+ <!-- Needed for GMSCore Location API test only -->
+ <permission name="android.permission.LOCATION_BYPASS"/>
<!-- Needed for test only -->
<permission name="android.permission.BATTERY_PREDICTION"/>
<permission name="android.permission.BATTERY_STATS"/>
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index c6731d112b93..48dd3e6c451b 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -1347,7 +1347,8 @@ public class HardwareRenderer {
nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
- defaultDisplay.getPresentationDeadlineNanos());
+ defaultDisplay.getPresentationDeadlineNanos(),
+ defaultDisplay.getOverlaySupport().supportFp16ForHdr());
mDisplayInitialized = true;
}
@@ -1527,7 +1528,8 @@ public class HardwareRenderer {
private static native void nSetDisplayDensityDpi(int densityDpi);
private static native void nInitDisplayInfo(int width, int height, float refreshRate,
- int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos);
+ int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos,
+ boolean supportsFp16ForHdr);
private static native void nSetDrawingEnabled(boolean drawingEnabled);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index fb0a9db6a20b..7e9c4189dabb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -41,7 +41,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d76ad3d27c70..48c5f64e0dd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -109,7 +109,6 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
if (mTaskViewTransitions != null) {
mTaskViewTransitions.addTaskView(this);
}
- setUseAlpha();
getHolder().addCallback(this);
mGuard.open("release");
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 591e3476ecd9..215308d9e96e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -130,6 +130,10 @@ class ActivityEmbeddingAnimationAdapter {
if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0);
+ } else if (mAnimation.hasExtension()) {
+ // Allow the surface to be shown in its original bounds in case we want to use edge
+ // extensions.
+ cropRect.union(mChange.getEndAbsBounds());
}
// cropRect is in absolute coordinate, so we need to translate it to surface top left.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 756d80204833..490975cce956 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -21,6 +21,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import android.animation.Animator;
@@ -45,6 +46,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/** To run the ActivityEmbedding animations. */
class ActivityEmbeddingAnimationRunner {
@@ -65,10 +67,31 @@ class ActivityEmbeddingAnimationRunner {
void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
+ // There may be some surface change that we want to apply after the start transaction is
+ // applied to make sure the surface is ready.
+ final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+ new ArrayList<>();
final Animator animator = createAnimator(info, startTransaction, finishTransaction,
- () -> mController.onAnimationFinished(transition));
- startTransaction.apply();
- animator.start();
+ () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
+
+ // Start the animation.
+ if (!postStartTransactionCallbacks.isEmpty()) {
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ startTransaction.apply(true /* sync */);
+
+ // Run tasks that require startTransaction to already be applied
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ postStartTransactionCallback.accept(t);
+ }
+ t.apply();
+ animator.start();
+ } else {
+ startTransaction.apply();
+ animator.start();
+ }
}
/**
@@ -85,9 +108,13 @@ class ActivityEmbeddingAnimationRunner {
Animator createAnimator(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Runnable animationFinishCallback) {
- final List<ActivityEmbeddingAnimationAdapter> adapters =
- createAnimationAdapters(info, startTransaction, finishTransaction);
+ @NonNull Runnable animationFinishCallback,
+ @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
+ final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
+ startTransaction);
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
+ adapters);
+ addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
long duration = 0;
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -131,8 +158,7 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -148,25 +174,23 @@ class ActivityEmbeddingAnimationRunner {
return createChangeAnimationAdapters(info, startTransaction);
}
if (Transitions.isClosingType(info.getType())) {
- return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
+ return createCloseAnimationAdapters(info);
}
- return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
+ return createOpenAnimationAdapters(info);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
- true /* isOpening */, mAnimationSpec::loadOpenAnimation);
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, true /* isOpening */,
+ mAnimationSpec::loadOpenAnimation);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
- false /* isOpening */, mAnimationSpec::loadCloseAnimation);
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, false /* isOpening */,
+ mAnimationSpec::loadCloseAnimation);
}
/**
@@ -175,8 +199,7 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
+ @NonNull TransitionInfo info, boolean isOpening,
@NonNull AnimationProvider animationProvider) {
// We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
@@ -200,8 +223,7 @@ class ActivityEmbeddingAnimationRunner {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, startTransaction, finishTransaction, animationProvider,
- openingWholeScreenBounds);
+ info, change, animationProvider, openingWholeScreenBounds);
if (isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -209,8 +231,7 @@ class ActivityEmbeddingAnimationRunner {
}
for (TransitionInfo.Change change : closingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, startTransaction, finishTransaction, animationProvider,
- closingWholeScreenBounds);
+ info, change, animationProvider, closingWholeScreenBounds);
if (!isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -219,20 +240,51 @@ class ActivityEmbeddingAnimationRunner {
return adapters;
}
+ /** Adds edge extension to the surfaces that have such an animation property. */
+ private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
+ @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ final Animation animation = adapter.mAnimation;
+ if (!animation.hasExtension()) {
+ continue;
+ }
+ final TransitionInfo.Change change = adapter.mChange;
+ if (Transitions.isOpeningType(adapter.mChange.getMode())) {
+ // Need to screenshot after startTransaction is applied otherwise activity
+ // may not be visible or ready yet.
+ postStartTransactionCallbacks.add(
+ t -> edgeExtendWindow(change, animation, t, finishTransaction));
+ } else {
+ // Can screenshot now (before startTransaction is applied)
+ edgeExtendWindow(change, animation, startTransaction, finishTransaction);
+ }
+ }
+ }
+
+ /** Adds background color to the transition if any animation has such a property. */
+ private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
+ adapter.mAnimation, 0 /* defaultColor */);
+ if (backgroundColor != 0) {
+ // We only need to show one color.
+ addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
+ finishTransaction);
+ return;
+ }
+ }
+ }
+
@NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
- // We may want to show a background color for open/close transition.
- final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
- 0 /* defaultColor */);
- if (backgroundColor != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
- finishTransaction);
- }
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index eb6ac7615266..58b23667dc18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -181,15 +181,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_open_enter
: com.android.internal.R.anim.task_fragment_clear_top_open_exit);
} else {
+ // Use the same edge extension animation as regular activity open.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_open_enter
- : com.android.internal.R.anim.task_fragment_open_exit);
+ ? com.android.internal.R.anim.activity_open_enter
+ : com.android.internal.R.anim.activity_open_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are opening at the same time, the animation applied to each will be the same.
@@ -205,15 +205,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_close_enter
: com.android.internal.R.anim.task_fragment_clear_top_close_exit);
} else {
+ // Use the same edge extension animation as regular activity close.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_close_enter
- : com.android.internal.R.anim.task_fragment_close_exit);
+ ? com.android.internal.R.anim.activity_close_enter
+ : com.android.internal.R.anim.activity_close_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are closing at the same time, the animation applied to each will be the same.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2d9c2a9145ab..f544a7c32a88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -938,7 +938,6 @@ public class BubbleStackView extends FrameLayout
addView(mAnimatingOutSurfaceContainer);
mAnimatingOutSurfaceView = new SurfaceView(getContext());
- mAnimatingOutSurfaceView.setUseAlpha();
mAnimatingOutSurfaceView.setZOrderOnTop(true);
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 30124a5363a4..616d447247de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -745,6 +745,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Directly move PiP to its final destination bounds without animation.
mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
+
+ // if the pip window size is beyond allowed bounds user resize to normal bounds
+ if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
+ || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
+ || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y
+ || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) {
+ mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction);
+ }
+
} else {
updateDisplayLayout.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 89d85e4b292d..41ff0b35a035 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -96,6 +96,7 @@ public class PipResizeGestureHandler {
private final Rect mDisplayBounds = new Rect();
private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
+ private final Consumer<Rect> mUpdateResizeBoundsCallback;
private int mDelta;
private float mTouchSlop;
@@ -137,6 +138,13 @@ public class PipResizeGestureHandler {
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mUpdateResizeBoundsCallback = (rect) -> {
+ mUserResizeBounds.set(rect);
+ mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
}
public void init() {
@@ -508,15 +516,50 @@ public class PipResizeGestureHandler {
}
}
+ private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
+ final int leftEdge = bounds.left;
+
+
+ final int fromLeft = Math.abs(leftEdge - movementBounds.left);
+ final int fromRight = Math.abs(movementBounds.right - leftEdge);
+
+ // The PIP will be snapped to either the right or left edge, so calculate which one
+ // is closest to the current position.
+ final int newLeft = fromLeft < fromRight
+ ? movementBounds.left : movementBounds.right;
+
+ bounds.offsetTo(newLeft, mLastResizeBounds.top);
+ }
+
+ /**
+ * Resizes the pip window and updates user-resized bounds.
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ Rect finalBounds = new Rect(bounds);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds);
+
+ // snap the target bounds to the either left or right edge, by choosing the closer one
+ snapToMovementBoundsEdge(finalBounds, movementBounds);
+
+ // apply the requested snap fraction onto the target bounds
+ mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction);
+
+ // resize from current bounds to target bounds without animation
+ mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null);
+ // set the flag that pip has been resized
+ mPipBoundsState.setHasUserResizedPip(true);
+
+ // finish the resize operation and update the state of the bounds
+ mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
+ }
+
private void finishResize() {
if (!mLastResizeBounds.isEmpty()) {
- final Consumer<Rect> callback = (rect) -> {
- mUserResizeBounds.set(mLastResizeBounds);
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
- resetState();
- };
-
// Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
// position correctly. Drag-resize does not need to move, so just finalize resize.
if (mOngoingPinchToResize) {
@@ -526,24 +569,23 @@ public class PipResizeGestureHandler {
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
}
- final int leftEdge = mLastResizeBounds.left;
- final Rect movementBounds =
- mPipBoundsAlgorithm.getMovementBounds(mLastResizeBounds);
- final int fromLeft = Math.abs(leftEdge - movementBounds.left);
- final int fromRight = Math.abs(movementBounds.right - leftEdge);
- // The PIP will be snapped to either the right or left edge, so calculate which one
- // is closest to the current position.
- final int newLeft = fromLeft < fromRight
- ? movementBounds.left : movementBounds.right;
- mLastResizeBounds.offsetTo(newLeft, mLastResizeBounds.top);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm
+ .getMovementBounds(mLastResizeBounds);
+
+ // snap mLastResizeBounds to the correct edge based on movement bounds
+ snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, callback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
+ PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
+ mUpdateResizeBoundsCallback);
}
final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
mPipDismissTargetHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 1f3f31e025a0..975d4bba276e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -825,6 +825,16 @@ public class PipTouchHandler {
}
/**
+ * Resizes the pip window and updates user resized bounds
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+ }
+
+ /**
* Gesture controlling normal movement of the PIP.
*/
private class DefaultPipTouchGesture extends PipTouchGesture {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 91c153e7b8fd..9c2c2fa8598a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -59,6 +59,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
@@ -76,10 +77,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Canvas;
import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@ import android.os.IBinder;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -97,7 +94,6 @@ import android.view.WindowManager.TransitionType;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
-import android.window.ScreenCapture;
import android.window.TransitionInfo;
import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
@@ -526,123 +522,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
- private void edgeExtendWindow(TransitionInfo.Change change,
- Animation a, SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction) {
- // Do not create edge extension surface for transfer starting window change.
- // The app surface could be empty thus nothing can draw on the hardware renderer, which will
- // block this thread when calling Surface#unlockCanvasAndPost.
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- return;
- }
- final Transformation transformationAtStart = new Transformation();
- a.getTransformationAt(0, transformationAtStart);
- final Transformation transformationAtEnd = new Transformation();
- a.getTransformationAt(1, transformationAtEnd);
-
- // We want to create an extension surface that is the maximal size and the animation will
- // take care of cropping any part that overflows.
- final Insets maxExtensionInsets = Insets.min(
- transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
- final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
- change.getEndAbsBounds().height());
- final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
- change.getEndAbsBounds().width());
- if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = maxExtensionInsets.left;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Left Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = 0;
- final int yPos = maxExtensionInsets.top;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Top Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = targetSurfaceWidth;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Right Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = maxExtensionInsets.left;
- final int yPos = targetSurfaceHeight;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Bottom Edge Extension", startTransaction, finishTransaction);
- }
- }
-
- private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction) {
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setParent(surfaceToExtend)
- .setHidden(true)
- .setCallsite("DefaultTransitionHandler#startAnimation")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
- .setSourceCrop(edgeBounds)
- .setFrameScale(1)
- .setPixelFormat(PixelFormat.RGBA_8888)
- .setChildrenOnly(true)
- .setAllowProtected(true)
- .build();
- final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
- ScreenCapture.captureLayers(captureArgs);
-
- if (edgeBuffer == null) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Failed to capture edge of window.");
- return null;
- }
-
- android.graphics.BitmapShader shader =
- new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
- android.graphics.Shader.TileMode.CLAMP,
- android.graphics.Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
-
- final Surface surface = new Surface(edgeExtensionLayer);
- Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
- surface.release();
-
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
- finishTransaction.remove(edgeExtensionLayer);
-
- return edgeExtensionLayer;
- }
-
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index efee6f40b53e..e3382211da79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
@@ -34,10 +35,20 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Shader;
import android.os.SystemProperties;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
@@ -217,4 +228,126 @@ public class TransitionAnimationHelper {
.show(animationBackgroundSurface);
finishTransaction.remove(animationBackgroundSurface);
}
+
+ /**
+ * Adds edge extension surface to the given {@code change} for edge extension animation.
+ */
+ public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
+ @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ // Do not create edge extension surface for transfer starting window change.
+ // The app surface could be empty thus nothing can draw on the hardware renderer, which will
+ // block this thread when calling Surface#unlockCanvasAndPost.
+ if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+ return;
+ }
+ final Transformation transformationAtStart = new Transformation();
+ a.getTransformationAt(0, transformationAtStart);
+ final Transformation transformationAtEnd = new Transformation();
+ a.getTransformationAt(1, transformationAtEnd);
+
+ // We want to create an extension surface that is the maximal size and the animation will
+ // take care of cropping any part that overflows.
+ final Insets maxExtensionInsets = Insets.min(
+ transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+ final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+ change.getEndAbsBounds().height());
+ final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+ change.getEndAbsBounds().width());
+ if (maxExtensionInsets.left < 0) {
+ final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.left, targetSurfaceHeight);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Left Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.top < 0) {
+ final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.top);
+ final int xPos = 0;
+ final int yPos = maxExtensionInsets.top;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Top Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.right < 0) {
+ final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.right, targetSurfaceHeight);
+ final int xPos = targetSurfaceWidth;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Right Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.bottom < 0) {
+ final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.bottom);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = targetSurfaceHeight;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Bottom Edge Extension", startTransaction, finishTransaction);
+ }
+ }
+
+ /**
+ * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
+ * animation.
+ */
+ private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
+ @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
+ @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setParent(surfaceToExtend)
+ .setHidden(true)
+ .setCallsite("TransitionAnimationHelper#createExtensionSurface")
+ .setOpaque(true)
+ .setBufferSize(extensionRect.width(), extensionRect.height())
+ .build();
+
+ final ScreenCapture.LayerCaptureArgs captureArgs =
+ new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
+ .setSourceCrop(edgeBounds)
+ .setFrameScale(1)
+ .setPixelFormat(PixelFormat.RGBA_8888)
+ .setChildrenOnly(true)
+ .setAllowProtected(true)
+ .build();
+ final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
+ ScreenCapture.captureLayers(captureArgs);
+
+ if (edgeBuffer == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Failed to capture edge of window.");
+ return null;
+ }
+
+ final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
+
+ final Surface surface = new Surface(edgeExtensionLayer);
+ final Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
+
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+ finishTransaction.remove(edgeExtensionLayer);
+
+ return edgeExtensionLayer;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index aaaccd837298..394d6f6bf731 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -322,6 +322,11 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_SYSTEM_WINDOW) != 0) {
+ // Currently system windows are controlled by WindowState, so don't change their
+ // surfaces. Otherwise their window tokens could be hidden unexpectedly.
+ continue;
+ }
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 7d498dcb35f7..1fc0375e9feb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -92,7 +92,7 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
}
/** Checks that [pipApp] window is animated towards default position in right bottom corner */
- @FlakyTest(bugId = 251135384)
+ @Presubmit
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
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 813ac5dad82b..84a8c0a59f32 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
@@ -26,7 +26,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
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.isRotated
+import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -79,10 +79,18 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
secondaryApp.windowMatchesAnyOf(window)
} ?: return@add false
- if (testSpec.startRotation.isRotated()) {
- return@add primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ if (isLandscape(testSpec.endRotation)) {
+ return@add if (testSpec.isTablet) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
} else {
- return@add primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ return@add if (testSpec.isTablet) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
}
}.waitForAndVerify()
}
@@ -101,14 +109,27 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds
?: return@add false
- if (testSpec.startRotation.isRotated()) {
- return@add primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ if (isLandscape(testSpec.endRotation)) {
+ return@add if (testSpec.isTablet) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
} else {
- return@add primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ return@add if (testSpec.isTablet) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
}
}.waitForAndVerify()
}
+ private fun isLandscape(rotation: Int): Boolean {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return displayBounds.width > displayBounds.height
+ }
+
@IwTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 98b59126227c..79070b1469be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -40,6 +40,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+
/**
* Tests for {@link ActivityEmbeddingAnimationRunner}.
*
@@ -62,13 +64,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
final TransitionInfo.Change embeddingChange = createChange();
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
info.addChange(embeddingChange);
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
- finishCallback.capture());
+ finishCallback.capture(), any());
verify(mStartTransaction).apply();
verify(mAnimator).start();
verifyNoMoreInteractions(mFinishTransaction);
@@ -88,7 +90,8 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
info.addChange(embeddingChange);
final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction,
- () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+ () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+ new ArrayList());
// The animation should be empty when it is behind starting window.
assertEquals(0, animator.getDuration());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 3792e8361284..54a12ab999c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -56,13 +56,12 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
@Mock
SurfaceControl.Transaction mFinishTransaction;
@Mock
- Transitions.TransitionFinishCallback mFinishCallback;
- @Mock
Animator mAnimator;
ActivityEmbeddingController mController;
ActivityEmbeddingAnimationRunner mAnimRunner;
ActivityEmbeddingAnimationSpec mAnimSpec;
+ Transitions.TransitionFinishCallback mFinishCallback;
@CallSuper
@Before
@@ -75,9 +74,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
assertNotNull(mAnimRunner);
mAnimSpec = mAnimRunner.mAnimationSpec;
assertNotNull(mAnimSpec);
+ mFinishCallback = (wct, wctCB) -> {};
spyOn(mController);
spyOn(mAnimRunner);
spyOn(mAnimSpec);
+ spyOn(mFinishCallback);
}
/** Creates a mock {@link TransitionInfo.Change}. */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index baecf6fe6673..4d98b6ba4f7a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -55,7 +55,7 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Before
public void setup() {
super.setUp();
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index dba037db72eb..3bd2ae76ebfd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.phone;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipResizeGestureHandlerTest extends ShellTestCase {
+ private static final float DEFAULT_SNAP_FRACTION = 2.0f;
private static final int STEP_SIZE = 40;
private final MotionEvent.PointerProperties[] mPp = new MotionEvent.PointerProperties[2];
@@ -196,6 +198,51 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
< mPipBoundsState.getBounds().width());
}
+ @Test
+ public void testUserResizeTo() {
+ // resizing the bounds to normal bounds at first
+ mPipResizeGestureHandler.userResizeTo(mPipBoundsState.getNormalBounds(),
+ DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(mPipBoundsState.getNormalBounds());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleFinishResizePip(any(), any());
+
+ // bounds with max size
+ final Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+
+ // resizing the bounds to maximum bounds the second time
+ mPipResizeGestureHandler.userResizeTo(maxBounds, DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(maxBounds);
+
+ // another call to scheduleUserResizePip() and scheduleFinishResizePip() makes
+ // the total number of invocations 2 for each method
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleFinishResizePip(any(), any());
+ }
+
+ private void assertPipBoundsUserResizedTo(Rect bounds) {
+ // check user-resized bounds
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().width(), bounds.width());
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().height(), bounds.height());
+
+ // check if the bounds are the same
+ assertEquals(mPipBoundsState.getBounds().width(), bounds.width());
+ assertEquals(mPipBoundsState.getBounds().height(), bounds.height());
+
+ // a flag should be set to indicate pip has been resized by the user
+ assertTrue(mPipBoundsState.hasUserResizedPip());
+ }
+
private MotionEvent obtainMotionEvent(int action, int topLeft, int bottomRight) {
final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[2];
for (int i = 0; i < 2; i++) {
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index f06fa2418003..0240c86d5f45 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -104,6 +104,10 @@ void DeviceInfo::setWideColorDataspace(ADataSpace dataspace) {
}
}
+void DeviceInfo::setSupportFp16ForHdr(bool supportFp16ForHdr) {
+ get()->mSupportFp16ForHdr = supportFp16ForHdr;
+}
+
void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) {
mVsyncPeriod = vsyncPeriod;
}
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 2e6e36a9ff22..577780bbb5e0 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -59,6 +59,9 @@ public:
}
static void setWideColorDataspace(ADataSpace dataspace);
+ static void setSupportFp16ForHdr(bool supportFp16ForHdr);
+ static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+
// this value is only valid after the GPU has been initialized and there is a valid graphics
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
@@ -88,6 +91,7 @@ private:
int mMaxTextureSize;
sk_sp<SkColorSpace> mWideColorSpace = SkColorSpace::MakeSRGB();
+ bool mSupportFp16ForHdr = false;
SkColorType mWideColorType = SkColorType::kN32_SkColorType;
int mDisplaysSize = 0;
int mPhysicalDisplayIndex = -1;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 704fba9241b6..f603e231e49b 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -867,17 +867,19 @@ static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass,
DeviceInfo::setDensity(density);
}
-static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth,
+static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv* env, jclass, jint physicalWidth,
jint physicalHeight, jfloat refreshRate,
jint wideColorDataspace,
jlong appVsyncOffsetNanos,
- jlong presentationDeadlineNanos) {
+ jlong presentationDeadlineNanos,
+ jboolean supportFp16ForHdr) {
DeviceInfo::setWidth(physicalWidth);
DeviceInfo::setHeight(physicalHeight);
DeviceInfo::setRefreshRate(refreshRate);
DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace));
DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
+ DeviceInfo::setSupportFp16ForHdr(supportFp16ForHdr);
}
static void android_view_ThreadedRenderer_setDrawingEnabled(JNIEnv*, jclass, jboolean enabled) {
@@ -1027,7 +1029,7 @@ static const JNINativeMethod gMethods[] = {
{"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
- {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+ {"nInitDisplayInfo", "(IIFIJJZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
{"isWebViewOverlaysEnabled", "()Z",
(void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 6aead4337245..08655ca6ad6b 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -87,6 +87,13 @@ public class AudioMixingRule {
* parameter is an instance of {@link java.lang.Integer}.
*/
public static final int RULE_MATCH_USERID = 0x1 << 3;
+ /**
+ * A rule requiring the audio session id of the audio stream to match that specified.
+ * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where Object
+ * parameter is an instance of {@link java.lang.Integer}.
+ * @see android.media.AudioTrack.Builder#setSessionId
+ */
+ public static final int RULE_MATCH_AUDIO_SESSION_ID = 0x1 << 4;
private final static int RULE_EXCLUSION_MASK = 0x8000;
/**
@@ -115,6 +122,13 @@ public class AudioMixingRule {
public static final int RULE_EXCLUDE_USERID =
RULE_EXCLUSION_MASK | RULE_MATCH_USERID;
+ /**
+ * @hide
+ * A rule requiring the audio session id information to differ.
+ */
+ public static final int RULE_EXCLUDE_AUDIO_SESSION_ID =
+ RULE_EXCLUSION_MASK | RULE_MATCH_AUDIO_SESSION_ID;
+
/** @hide */
public static final class AudioMixMatchCriterion {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -166,6 +180,7 @@ public class AudioMixingRule {
break;
case RULE_MATCH_UID:
case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
dest.writeInt(mIntProp);
break;
default:
@@ -315,6 +330,7 @@ public class AudioMixingRule {
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
case RULE_MATCH_UID:
case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
return true;
default:
return false;
@@ -338,6 +354,7 @@ public class AudioMixingRule {
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
case RULE_MATCH_UID:
case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
return true;
default:
return false;
@@ -445,7 +462,8 @@ public class AudioMixingRule {
* @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
* {@link AudioMixingRule#RULE_MATCH_UID} or
- * {@link AudioMixingRule#RULE_MATCH_USERID}.
+ * {@link AudioMixingRule#RULE_MATCH_USERID} or
+ * {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID}.
* @param property see the definition of each rule for the type to use (either an
* {@link AudioAttributes} or an {@link java.lang.Integer}).
* @return the same Builder instance.
@@ -476,7 +494,8 @@ public class AudioMixingRule {
* @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
* {@link AudioMixingRule#RULE_MATCH_UID} or
- * {@link AudioMixingRule#RULE_MATCH_USERID}.
+ * {@link AudioMixingRule#RULE_MATCH_USERID} or
+ * {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID}.
* @param property see the definition of each rule for the type to use (either an
* {@link AudioAttributes} or an {@link java.lang.Integer}).
* @return the same Builder instance.
@@ -606,9 +625,12 @@ public class AudioMixingRule {
* @param intProp an integer property to match or exclude, null if not used.
* @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
- * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET},
* {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
- * {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
+ * {@link AudioMixingRule#RULE_MATCH_UID},
+ * {@link AudioMixingRule#RULE_EXCLUDE_UID},
+ * {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID},
+ * {@link AudioMixingRule#RULE_EXCLUDE_AUDIO_SESSION_ID}
* {@link AudioMixingRule#RULE_MATCH_USERID},
* {@link AudioMixingRule#RULE_EXCLUDE_USERID}.
* @return the same Builder instance.
@@ -645,6 +667,7 @@ public class AudioMixingRule {
break;
case RULE_MATCH_UID:
case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
break;
default:
@@ -666,6 +689,7 @@ public class AudioMixingRule {
break;
case RULE_MATCH_UID:
case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
intProp = new Integer(in.readInt());
break;
default:
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index f3731b68f787..440447e5ec1d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -217,6 +217,14 @@ public class AudioPolicyConfig implements Parcelable {
textDump += " exclude userId ";
textDump += criterion.mIntProp;
break;
+ case AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID:
+ textDump += " match audio session id";
+ textDump += criterion.mIntProp;
+ break;
+ case AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID:
+ textDump += " exclude audio session id ";
+ textDump += criterion.mIntProp;
+ break;
default:
textDump += "invalid rule!";
}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
index ad7ab971d5f2..a83e7d3a6ef1 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
@@ -22,9 +22,11 @@ import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID;
import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -61,12 +63,14 @@ public class AudioMixingRuleUnitTests {
new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
private static final int TEST_UID = 42;
private static final int OTHER_UID = 77;
+ private static final int TEST_SESSION_ID = 1234;
@Test
public void testConstructValidRule() {
AudioMixingRule rule = new AudioMixingRule.Builder()
.addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
.addMixRule(RULE_MATCH_UID, TEST_UID)
+ .excludeMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
.build();
// Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
@@ -74,7 +78,8 @@ public class AudioMixingRuleUnitTests {
assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
assertThat(rule.getCriteria(), containsInAnyOrder(
isAudioMixMatchUsageCriterion(USAGE_MEDIA),
- isAudioMixMatchUidCriterion(TEST_UID)));
+ isAudioMixMatchUidCriterion(TEST_UID),
+ isAudioMixExcludeSessionCriterion(TEST_SESSION_ID)));
}
@Test
@@ -183,6 +188,30 @@ public class AudioMixingRuleUnitTests {
.build());
}
+ @Test
+ public void sessionIdRuleCompatibleWithPlayersMix() {
+ int sessionId = 42;
+ AudioMixingRule rule = new AudioMixingRule.Builder()
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, sessionId)
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .build();
+
+ assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
+ assertThat(rule.getCriteria(), containsInAnyOrder(isAudioMixSessionCriterion(sessionId)));
+ }
+
+ @Test
+ public void sessionIdRuleCompatibleWithInjectorMix() {
+ AudioMixingRule rule = new AudioMixingRule.Builder()
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .build();
+
+ assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
+ assertThat(rule.getCriteria(),
+ containsInAnyOrder(isAudioMixSessionCriterion(TEST_SESSION_ID)));
+ }
+
private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
@@ -257,5 +286,31 @@ public class AudioMixingRuleUnitTests {
return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
}
+ private static Matcher isAudioMixSessionCriterion(int sessionId, boolean exclude) {
+ return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("sessionId mix criterion") {
+ @Override
+ public boolean matchesSafely(AudioMixMatchCriterion item) {
+ int excludeRule =
+ exclude ? RULE_EXCLUDE_AUDIO_SESSION_ID : RULE_MATCH_AUDIO_SESSION_ID;
+ return item.getRule() == excludeRule && item.getIntProp() == sessionId;
+ }
+
+ @Override
+ public void describeMismatchSafely(
+ AudioMixMatchCriterion item, Description mismatchDescription) {
+ mismatchDescription.appendText(
+ String.format("is not %s criterion with session id %d",
+ exclude ? "exclude" : "match", sessionId));
+ }
+ };
+ }
+
+ private static Matcher isAudioMixSessionCriterion(int sessionId) {
+ return isAudioMixSessionCriterion(sessionId, /*exclude=*/ false);
+ }
+
+ private static Matcher isAudioMixExcludeSessionCriterion(int sessionId) {
+ return isAudioMixSessionCriterion(sessionId, /*exclude=*/ true);
+ }
}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
index a207bf1d5359..fa186c3dc0d3 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
@@ -304,13 +304,13 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
for (int j = 0; j < NUM_EFFECTS; j++) {
effects[j] = new BassBoost(0, mSession);
effects[j].setControlStatusListener(mEffectListener);
- yield();
+ this.yield();
}
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
effects[j].release();
effects[j] = null;
- yield();
+ this.yield();
}
}
Log.w(TAG, "HammerReleaseTest ended");
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
index dcfe11a79ef9..5d91a2e99230 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
@@ -251,13 +251,13 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
for (int i = 0; i < NUM_ITERATIONS; i++) {
for (int j = 0; j < NUM_EFFECTS; j++) {
effects[j] = new Visualizer(mSession);
- yield();
+ this.yield();
}
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
effects[j].release();
effects[j] = null;
- yield();
+ this.yield();
}
}
Log.w(TAG, "HammerReleaseTest ended");
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index db54ae3aeee9..d4439f9e7e64 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -39,7 +39,6 @@
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingEnd="24dp"
- android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 56715b422069..a7e1a5954aea 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -363,13 +363,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
}
- private void onAssociationCreated(@NonNull AssociationInfo association) {
- if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
-
- // Don't need to notify the app, CdmService has already done that. Just finish.
- setResultAndFinish(association, RESULT_OK);
- }
-
private void cancel(boolean discoveryTimeout, boolean userRejected) {
if (DEBUG) {
Log.i(TAG, "cancel(), discoveryTimeout="
@@ -413,7 +406,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
- if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association);
+ Log.i(TAG, "setResultAndFinish(), association="
+ + (association == null ? "null" : association)
+ + "resultCode=" + resultCode);
final Intent data = new Intent();
if (association != null) {
@@ -652,14 +647,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements
new ResultReceiver(Handler.getMain()) {
@Override
protected void onReceiveResult(int resultCode, Bundle data) {
- if (resultCode != RESULT_CODE_ASSOCIATION_CREATED) {
- throw new RuntimeException("Unknown result code: " + resultCode);
+ if (resultCode == RESULT_CODE_ASSOCIATION_CREATED) {
+ final AssociationInfo association = data.getParcelable(
+ EXTRA_ASSOCIATION, AssociationInfo.class);
+ requireNonNull(association);
+ setResultAndFinish(association, RESULT_OK);
+ } else {
+ setResultAndFinish(null, resultCode);
}
-
- final AssociationInfo association = data.getParcelable(EXTRA_ASSOCIATION);
- requireNonNull(association);
-
- onAssociationCreated(association);
}
};
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
new file mode 100644
index 000000000000..96cc02f94361
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class CredentialEntryUi(
+ val userName: CharSequence,
+ val displayName: CharSequence?,
+ val icon: Icon?,
+ // TODO: add last used.
+) {
+ companion object {
+ fun fromSlice(slice: Slice): CredentialEntryUi {
+ val items = slice.items
+
+ var title: String? = null
+ var subTitle: String? = null
+ var icon: Icon? = null
+
+ items.forEach {
+ if (it.hasHint(Entry.HINT_ICON)) {
+ icon = it.icon
+ } else if (it.hasHint(Entry.HINT_SUBTITLE)) {
+ subTitle = it.text.toString()
+ } else if (it.hasHint(Entry.HINT_TITLE)) {
+ title = it.text.toString()
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return CredentialEntryUi(title!!, subTitle, icon)
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 46bf19c64759..8db547a49c88 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -1,3 +1,19 @@
+/*
+ * 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
import android.app.slice.Slice
@@ -9,11 +25,8 @@ import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.graphics.drawable.Icon
import android.os.Binder
-import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.ProviderInfo
-import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -41,6 +54,39 @@ class CredentialManagerRepo(
) ?: testProviderList()
}
+ fun getCredentialInitialUiState(): GetCredentialUiState {
+ val providerList = GetFlowUtils.toProviderList(providerList, context)
+ return GetCredentialUiState(
+ providerList,
+ GetScreenState.CREDENTIAL_SELECTION,
+ providerList.first()
+ )
+ }
+
+ fun createPasskeyInitialUiState(): CreatePasskeyUiState {
+ val providerList = CreateFlowUtils.toProviderList(providerList, context)
+ return CreatePasskeyUiState(
+ providers = providerList,
+ currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ )
+ }
+
+ companion object {
+ lateinit var repo: CredentialManagerRepo
+
+ fun setup(
+ context: Context,
+ intent: Intent,
+ ) {
+ repo = CredentialManagerRepo(context, intent)
+ }
+
+ fun getInstance(): CredentialManagerRepo {
+ return repo
+ }
+ }
+
+ // TODO: below are prototype functionalities. To be removed for productionization.
private fun testProviderList(): List<ProviderData> {
return listOf(
ProviderData(
@@ -94,143 +140,4 @@ class CredentialManagerRepo(
slice
)
}
-
- private fun getCredentialProviderList():
- List<com.android.credentialmanager.getflow.ProviderInfo> {
- return listOf(
- com.android.credentialmanager.getflow.ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Google Password Manager",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- credentialOptions = listOf(
- CredentialOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@gmail.com",
- id = "id-1",
- ),
- CredentialOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett Work",
- subtitle = "elisa.beckett.work@google.com",
- id = "id-2",
- ),
- )
- ),
- com.android.credentialmanager.getflow.ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Lastpass",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- credentialOptions = listOf(
- CredentialOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@lastpass.com",
- id = "id-1",
- ),
- )
- ),
- com.android.credentialmanager.getflow.ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Dashlane",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- credentialOptions = listOf(
- CredentialOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@dashlane.com",
- id = "id-1",
- ),
- )
- ),
- )
- }
-
- private fun createCredentialProviderList(): List<ProviderInfo> {
- return listOf(
- ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Google Password Manager",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- createOptions = listOf(
- CreateOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@gmail.com",
- id = "id-1",
- ),
- CreateOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett Work",
- subtitle = "elisa.beckett.work@google.com",
- id = "id-2",
- ),
- )
- ),
- ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Lastpass",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- createOptions = listOf(
- CreateOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@lastpass.com",
- id = "id-1",
- ),
- )
- ),
- ProviderInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = "Dashlane",
- appDomainName = "tribank.us",
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
- createOptions = listOf(
- CreateOptionInfo(
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
- title = "Elisa Backett",
- subtitle = "elisa.beckett@dashlane.com",
- id = "id-1",
- ),
- )
- ),
- )
- }
-
- fun getCredentialInitialUiState(): GetCredentialUiState {
- val providerList = getCredentialProviderList()
- return GetCredentialUiState(
- providerList,
- GetScreenState.CREDENTIAL_SELECTION,
- providerList.first()
- )
- }
-
- fun createPasskeyInitialUiState(): CreatePasskeyUiState {
- val providerList = createCredentialProviderList()
- return CreatePasskeyUiState(
- providers = providerList,
- currentScreenState = CreateScreenState.PASSKEY_INTRO,
- )
- }
-
- companion object {
- lateinit var repo: CredentialManagerRepo
-
- fun setup(
- context: Context,
- intent: Intent,
- ) {
- repo = CredentialManagerRepo(context, intent)
- }
-
- fun getInstance(): CredentialManagerRepo {
- return repo
- }
- }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 98c824cb77fc..b538ae79091f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -1,5 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.credentialmanager
+import android.credentials.ui.RequestInfo
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
@@ -16,14 +33,20 @@ class CredentialSelectorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CredentialManagerRepo.setup(this, intent)
- val startDestination = intent.extras?.getString(
- "start_destination",
- "CREATE_PASSKEY"
- ) ?: "CREATE_PASSKEY"
-
- setContent {
- CredentialSelectorTheme {
- CredentialManagerBottomSheet(startDestination)
+ val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
+ if (requestInfo != null) {
+ val requestType = requestInfo.type
+ setContent {
+ CredentialSelectorTheme {
+ CredentialManagerBottomSheet(requestType)
+ }
+ }
+ } else {
+ // TODO: prototype only code to be removed. In production should exit.
+ setContent {
+ CredentialSelectorTheme {
+ CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
new file mode 100644
index 000000000000..7159ab97202b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -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 com.android.credentialmanager
+
+import android.content.Context
+import android.credentials.ui.Entry
+import android.credentials.ui.ProviderData
+import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.getflow.ProviderInfo
+
+/** Utility functions for converting CredentialManager data structures to or from UI formats. */
+class GetFlowUtils {
+ companion object {
+
+ fun toProviderList(
+ providerDataList: List<ProviderData>,
+ context: Context,
+ ): List<ProviderInfo> {
+ return providerDataList.map {
+ ProviderInfo(
+ // TODO: replace to extract from the service data structure when available
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = it.packageName,
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
+ )
+ }
+ }
+
+
+ /* From service data structure to UI credential entry list representation. */
+ private fun toCredentialOptionInfoList(
+ credentialEntries: List<Entry>,
+ context: Context,
+ ): List<CredentialOptionInfo> {
+ return credentialEntries.map {
+ val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
+
+ // Consider directly move the UI object into the class.
+ return@map CredentialOptionInfo(
+ // TODO: remove fallbacks
+ icon = credentialEntryUi.icon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ title = credentialEntryUi.userName.toString(),
+ subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
+ id = it.entryId,
+ )
+ }
+ }
+ }
+}
+
+class CreateFlowUtils {
+ companion object {
+
+ fun toProviderList(
+ providerDataList: List<ProviderData>,
+ context: Context,
+ ): List<com.android.credentialmanager.createflow.ProviderInfo> {
+ return providerDataList.map {
+ com.android.credentialmanager.createflow.ProviderInfo(
+ // TODO: replace to extract from the service data structure when available
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = it.packageName,
+ appDomainName = "tribank.us",
+ credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+ createOptions = toCreationOptionInfoList(it.credentialEntries, context),
+ )
+ }
+ }
+
+ private fun toCreationOptionInfoList(
+ creationEntries: List<Entry>,
+ context: Context,
+ ): List<CreateOptionInfo> {
+ return creationEntries.map {
+ val saveEntryUi = SaveEntryUi.fromSlice(it.slice)
+
+ return@map CreateOptionInfo(
+ // TODO: remove fallbacks
+ icon = saveEntryUi.icon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ title = saveEntryUi.title.toString(),
+ subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
+ id = it.entryId,
+ )
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
new file mode 100644
index 000000000000..2b63e1d1cd69
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+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 title: CharSequence,
+ val subTitle: CharSequence?,
+ val icon: Icon?,
+ // TODO: add
+) {
+ companion object {
+ fun fromSlice(slice: Slice): SaveEntryUi {
+ val items = slice.items
+
+ var title: String? = null
+ var subTitle: String? = null
+ var icon: Icon? = null
+
+ items.forEach {
+ if (it.hasHint(Entry.HINT_ICON)) {
+ icon = it.icon
+ } else if (it.hasHint(Entry.HINT_SUBTITLE)) {
+ subTitle = it.text.toString()
+ } else if (it.hasHint(Entry.HINT_TITLE)) {
+ title = it.text.toString()
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return SaveEntryUi(title!!, subTitle, icon)
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
index 8bb80a1b02fd..b5e9fd00bb22 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
@@ -1,5 +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.credentialmanager.common
+import android.credentials.ui.RequestInfo
+
enum class DialogType {
CREATE_PASSKEY,
GET_CREDENTIALS,
@@ -8,10 +26,10 @@ enum class DialogType {
companion object {
fun toDialogType(value: String): DialogType {
- return try {
- valueOf(value)
- } catch (e: IllegalArgumentException) {
- UNKNOWN
+ return when (value) {
+ RequestInfo.TYPE_GET -> GET_CREDENTIALS
+ RequestInfo.TYPE_CREATE -> CREATE_PASSKEY
+ else -> UNKNOWN
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 044688b8c227..12f18460ce24 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.createflow
import android.graphics.drawable.Drawable
@@ -14,7 +30,7 @@ data class CreateOptionInfo(
val icon: Drawable,
val title: String,
val subtitle: String,
- val id: String,
+ val id: Int,
)
/** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 997519db111d..b61f4652ad22 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -222,7 +222,9 @@ fun MoreOptionSelectionCard(
) {
Column() {
TopAppBar(
- title = { Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1) },
+ title = {
+ Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1)
+ },
backgroundColor = lightBackgroundColor,
elevation = 0.dp,
navigationIcon =
@@ -343,7 +345,7 @@ fun NavigationButton(
@Composable
fun CreationSelectionCard(
providerInfo: ProviderInfo,
- onOptionSelected: (String) -> Unit,
+ onOptionSelected: (Int) -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
onMoreOptionSelected: (String) -> Unit,
@@ -414,7 +416,7 @@ fun CreationSelectionCard(
@ExperimentalMaterialApi
@Composable
-fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) {
+fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (Int) -> Unit) {
Chip(
modifier = Modifier.fillMaxWidth(),
onClick = {onOptionSelected(createOptionInfo.id)},
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 15300def6fda..5b70f9d91c54 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.createflow
import android.util.Log
@@ -42,7 +58,7 @@ class CreatePasskeyViewModel(
)
}
- fun onCreateOptionSelected(createOptionId: String) {
+ fun onCreateOptionSelected(createOptionId: Int) {
Log.d("Account Selector", "Option selected for creation: $createOptionId")
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 1ca70ed91598..0b188221e8b3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.getflow
import androidx.compose.foundation.Image
@@ -74,7 +90,7 @@ fun GetCredentialScreen(
@Composable
fun CredentialSelectionCard(
providerInfo: ProviderInfo,
- onOptionSelected: (String) -> Unit,
+ onOptionSelected: (Int) -> Unit,
onCancel: () -> Unit,
multiProvider: Boolean,
onMoreOptionSelected: () -> Unit,
@@ -149,7 +165,7 @@ fun CredentialSelectionCard(
@Composable
fun CredentialOptionRow(
credentialOptionInfo: CredentialOptionInfo,
- onOptionSelected: (String) -> Unit
+ onOptionSelected: (Int) -> Unit
) {
Chip(
modifier = Modifier.fillMaxWidth(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 06bcd7fedc6f..0fdd8ecd38c9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.getflow
import android.util.Log
@@ -20,7 +36,7 @@ class GetCredentialViewModel(
var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
private set
- fun onCredentailSelected(credentialId: String) {
+ fun onCredentailSelected(credentialId: Int) {
Log.d("Account Selector", "credential selected: $credentialId")
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 867e9c2acc63..acea8c9d6536 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.getflow
import android.graphics.drawable.Drawable
@@ -14,7 +30,7 @@ data class CredentialOptionInfo(
val icon: Drawable,
val title: String,
val subtitle: String,
- val id: String,
+ val id: Int,
)
/** The name of the current screen. */
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index b4b65d4c0ddc..b7ce518f1516 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "SpaPrivilegedLibTests"
+ },
+ {
+ "name": "SettingsSpaUnitTests"
}
]
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
index 8c9d42c053d2..36b58ad794dc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
@@ -22,6 +22,6 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
class GalleryApplication : Application() {
override fun onCreate() {
super.onCreate()
- SpaEnvironmentFactory.instance = GallerySpaEnvironment
+ SpaEnvironmentFactory.reset(GallerySpaEnvironment)
}
} \ No newline at end of file
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 aa457fe2c805..acb22dac9854 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
@@ -16,11 +16,10 @@
package com.android.settingslib.spa.gallery
-import android.os.Bundle
-import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.LocalLogger
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -49,19 +48,6 @@ enum class SettingsPageProviderEnum(val displayName: String) {
// Add your SPPs
}
-fun createSettingsPage(
- SppName: SettingsPageProviderEnum,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
-): SettingsPage {
- return SettingsPage.create(
- name = SppName.name,
- displayName = SppName.displayName,
- parameter = parameter,
- arguments = arguments,
- )
-}
-
object GallerySpaEnvironment : SpaEnvironment() {
override val pageProviderRepository = lazy {
SettingsPageProviderRepository(
@@ -82,7 +68,7 @@ object GallerySpaEnvironment : SpaEnvironment() {
ActionButtonPageProvider,
),
rootPages = listOf(
- createSettingsPage(SettingsPageProviderEnum.HOME)
+ HomePageProvider.createSettingsPage(),
)
)
}
@@ -90,4 +76,6 @@ object GallerySpaEnvironment : SpaEnvironment() {
override val browseActivityClass = GalleryMainActivity::class.java
override val entryProviderAuthorities = "com.android.spa.gallery.provider"
+
+ override val logger = LocalLogger()
}
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 33cd5f143733..e40775a95813 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
@@ -22,11 +22,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -40,9 +40,10 @@ import com.android.settingslib.spa.widget.scaffold.HomeScaffold
object HomePageProvider : SettingsPageProvider {
override val name = SettingsPageProviderEnum.HOME.name
+ override val displayName = SettingsPageProviderEnum.HOME.displayName
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = createSettingsPage(SettingsPageProviderEnum.HOME)
return listOf(
PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 5031fb445e06..82073104a3a7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -23,9 +23,9 @@ import com.android.settingslib.spa.framework.common.SettingsEntry
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.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -43,13 +43,13 @@ object ArgumentPageProvider : SettingsPageProvider {
}
override val name = SettingsPageProviderEnum.ARGUMENT.name
-
+ override val displayName = SettingsPageProviderEnum.ARGUMENT.displayName
override val parameter = ArgumentPageModel.parameter
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
- val owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments)
+ val owner = createSettingsPage(arguments)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
createEntry(owner, EntryEnum.STRING_PARAM)
@@ -86,7 +86,7 @@ object ArgumentPageProvider : SettingsPageProvider {
if (!ArgumentPageModel.isValidArgument(arguments)) return null
return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments),
+ owner = createSettingsPage(arguments),
displayName = "${name}_$stringParam",
)
// Set attributes
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index 107d3f316c23..e5e3c679a76a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -17,13 +17,13 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import android.util.Log
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.PageModel
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.getIntArg
@@ -32,6 +32,8 @@ import com.android.settingslib.spa.framework.util.navLink
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.widget.preference.PreferenceModel
+private const val TAG = "ArgumentPageModel"
+
// Defines all the resources for this page.
// In real Settings App, resources data is defined in xml, rather than SPP.
private const val PAGE_TITLE = "Sample page with arguments"
@@ -93,7 +95,9 @@ class ArgumentPageModel : PageModel() {
private var intParam: Int? = null
override fun initialize(arguments: Bundle?) {
- logMsg("init with args " + arguments.toString())
+ SpaEnvironmentFactory.instance.logger.message(
+ TAG, "Initialize with args " + arguments.toString()
+ )
this.arguments = arguments
stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
@@ -135,7 +139,3 @@ class ArgumentPageModel : PageModel() {
}
}
}
-
-private fun logMsg(message: String) {
- Log.d("ArgumentPageModel", message)
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 0f99c57ebcad..165eaa05c9d5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -20,8 +20,8 @@ import android.os.Bundle
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
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.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -31,22 +31,20 @@ private const val TITLE = "Category: Preference"
object PreferenceMainPageProvider : SettingsPageProvider {
override val name = "PreferenceMain"
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
return listOf(
- PreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
- SwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
- MainSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
+ PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = SettingsPage.create(name)).build(),
+ .setLink(fromPage = owner).build(),
)
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = owner)
.setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index f7f01eaa4a93..a2a913f46233 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,11 +30,12 @@ import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.createSettingsPage
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -44,13 +45,14 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Compan
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.logMsg
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.SettingsIcon
+private const val TAG = "PreferencePage"
+
object PreferencePageProvider : SettingsPageProvider {
// Defines all entry name in this page.
// Note that entry name would be used in log. DO NOT change it once it is set.
@@ -66,7 +68,9 @@ object PreferencePageProvider : SettingsPageProvider {
}
override val name = SettingsPageProviderEnum.PREFERENCE.name
- private val owner = createSettingsPage(SettingsPageProviderEnum.PREFERENCE)
+ override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
+ private val spaLogger = SpaEnvironmentFactory.instance.logger
+ private val owner = createSettingsPage()
private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
@@ -78,7 +82,7 @@ object PreferencePageProvider : SettingsPageProvider {
createEntry(EntryEnum.SIMPLE_PREFERENCE)
.setIsAllowSearch(true)
.setMacro {
- logMsg("create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
+ spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
}
.build()
@@ -87,7 +91,7 @@ object PreferencePageProvider : SettingsPageProvider {
createEntry(EntryEnum.SUMMARY_PREFERENCE)
.setIsAllowSearch(true)
.setMacro {
- logMsg("create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
+ spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
SimplePreferenceMacro(
title = SIMPLE_PREFERENCE_TITLE,
summary = SIMPLE_PREFERENCE_SUMMARY,
@@ -101,7 +105,7 @@ object PreferencePageProvider : SettingsPageProvider {
createEntry(EntryEnum.DISABLED_PREFERENCE)
.setIsAllowSearch(true)
.setMacro {
- logMsg("create macro for ${EntryEnum.DISABLED_PREFERENCE}")
+ spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
SimplePreferenceMacro(
title = DISABLE_PREFERENCE_TITLE,
summary = DISABLE_PREFERENCE_SUMMARY,
@@ -187,7 +191,7 @@ object PreferencePageProvider : SettingsPageProvider {
return SettingsEntryBuilder.createInject(owner = owner)
.setIsAllowSearch(true)
.setMacro {
- logMsg("create macro for INJECT entry")
+ spaLogger.message(TAG, "create macro for INJECT entry")
SimplePreferenceMacro(
title = PAGE_TITLE,
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index 1188e1e006b2..1e64b2edb60b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.gallery.preference
import android.os.Bundle
-import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
@@ -27,11 +26,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.common.PageModel
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+private const val TAG = "PreferencePageModel"
+
class PreferencePageModel : PageModel() {
companion object {
// Defines all the resources for this page.
@@ -53,12 +55,10 @@ class PreferencePageModel : PageModel() {
pageModel.initOnce()
return pageModel
}
-
- fun logMsg(message: String) {
- Log.d("PreferencePageModel", message)
- }
}
+ private val spaLogger = SpaEnvironmentFactory.instance.logger
+
private val asyncSummary = mutableStateOf(" ")
private val manualUpdater = mutableStateOf(0)
@@ -67,26 +67,25 @@ class PreferencePageModel : PageModel() {
private var tick = 0
private var updateJob: Job? = null
override fun onActive() {
- logMsg("autoUpdater.active")
+ spaLogger.message(TAG, "autoUpdater.active")
updateJob = viewModelScope.launch(Dispatchers.IO) {
while (true) {
delay(1000L)
tick++
- logMsg("autoUpdater.value $tick")
+ spaLogger.message(TAG, "autoUpdater.value $tick")
postValue(tick.toString())
}
}
}
override fun onInactive() {
- logMsg("autoUpdater.inactive")
+ spaLogger.message(TAG, "autoUpdater.inactive")
updateJob?.cancel()
}
}
override fun initialize(arguments: Bundle?) {
- logMsg("init with args " + arguments.toString())
-
+ spaLogger.message(TAG, "initialize with args " + arguments.toString())
viewModelScope.launch(Dispatchers.IO) {
delay(2000L)
asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
@@ -94,22 +93,22 @@ class PreferencePageModel : PageModel() {
}
fun getAsyncSummary(): State<String> {
- logMsg("getAsyncSummary")
+ spaLogger.message(TAG, "getAsyncSummary")
return asyncSummary
}
fun getManualUpdaterSummary(): State<String> {
- logMsg("getManualUpdaterSummary")
+ spaLogger.message(TAG, "getManualUpdaterSummary")
return derivedStateOf { manualUpdater.value.toString() }
}
fun manualUpdaterOnClick() {
- logMsg("manualUpdaterOnClick")
+ spaLogger.message(TAG, "manualUpdaterOnClick")
manualUpdater.value = manualUpdater.value + 1
}
fun getAutoUpdaterSummary(): LiveData<String> {
- logMsg("getAutoUpdaterSummary")
+ spaLogger.message(TAG, "getAutoUpdaterSummary")
return autoUpdater
}
}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 1d42e27ed9f2..8b29366ef6a3 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -29,6 +29,7 @@ android_library {
"androidx.compose.runtime_runtime",
"androidx.compose.runtime_runtime-livedata",
"androidx.compose.ui_ui-tooling-preview",
+ "androidx.lifecycle_lifecycle-livedata-ktx",
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
"lottie_compose",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 362953f97413..7e05e75804a7 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,13 +59,14 @@ android {
}
dependencies {
- api "androidx.appcompat:appcompat:1.6.0-rc01"
+ api "androidx.appcompat:appcompat:1.7.0-alpha01"
api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api 'androidx.navigation:navigation-compose:2.5.0'
- api 'com.google.android.material:material:1.6.1'
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.navigation:navigation-compose:2.5.0"
+ api "com.google.android.material:material:1.6.1"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
- implementation 'com.airbnb.android:lottie-compose:5.2.0'
+ implementation "com.airbnb.android:lottie-compose:5.2.0"
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d87c31b7f95c..476dd30b499b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -17,20 +17,25 @@
package com.android.settingslib.spa.framework
import android.os.Bundle
-import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.android.settingslib.spa.R
+import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
import com.android.settingslib.spa.framework.compose.localNavController
@@ -42,13 +47,19 @@ private const val NULL_PAGE_NAME = "NULL"
/**
* The Activity to render ALL SPA pages, and handles jumps between SPA pages.
+ *
* One can open any SPA page by:
- * $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination <SpaPageRoute>
- * For gallery, BrowseActivityComponent = com.android.settingslib.spa.gallery/.MainActivity
- * For SettingsGoogle, BrowseActivityComponent = com.android.settings/.spa.SpaActivity
+ * ```
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination <SpaPageRoute>
+ * ```
+ * - For Gallery, BrowseActivityComponent = com.android.settingslib.spa.gallery/.GalleryMainActivity
+ * - For Settings, BrowseActivityComponent = com.android.settings/.spa.SpaActivity
+ *
* Some examples:
- * $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
- * $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
+ * ```
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination HOME
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination ARGUMENT/bar/5
+ * ```
*/
open class BrowseActivity : ComponentActivity() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
@@ -56,7 +67,7 @@ open class BrowseActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
- Log.d(TAG, "onCreate")
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
SettingsTheme {
@@ -72,11 +83,43 @@ open class BrowseActivity : ComponentActivity() {
CompositionLocalProvider(navController.localNavController()) {
NavHost(navController, NULL_PAGE_NAME) {
composable(NULL_PAGE_NAME) {}
- for (page in sppRepository.getAllProviders()) {
+ for (spp in sppRepository.getAllProviders()) {
composable(
- route = page.name + page.parameter.navRoute(),
- arguments = page.parameter,
- ) { navBackStackEntry -> page.Page(navBackStackEntry.arguments) }
+ route = spp.name + spp.parameter.navRoute(),
+ arguments = spp.parameter,
+ ) { navBackStackEntry ->
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val spaLogger = spaEnvironment.logger
+ val sp = spp.createSettingsPage(arguments = navBackStackEntry.arguments)
+
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ spaLogger.event(
+ sp.id,
+ "enter page ${sp.formatDisplayTitle()}",
+ category = LogCategory.FRAMEWORK
+ )
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ spaLogger.event(
+ sp.id,
+ "leave page ${sp.formatDisplayTitle()}",
+ category = LogCategory.FRAMEWORK
+ )
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+
+ spp.Page(navBackStackEntry.arguments)
+ }
}
}
InitialDestinationNavigator()
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/DebugActivity.kt
index b28da06caf14..6f968180e243 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/DebugActivity.kt
@@ -35,6 +35,7 @@ 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
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@@ -69,7 +70,7 @@ open class DebugActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
- Log.d(TAG, "onCreate")
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
SettingsTheme {
@@ -94,7 +95,7 @@ open class DebugActivity : ComponentActivity() {
cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM)
val message = "Page Info: $route ($entryCount) " +
(if (hasRuntimeParam) "with" else "no") + "-runtime-params"
- Log.d(TAG, message)
+ spaEnvironment.logger.message(TAG, message, category = LogCategory.FRAMEWORK)
}
}
} catch (e: Exception) {
@@ -229,7 +230,9 @@ open class DebugActivity : ComponentActivity() {
putExtra(KEY_DESTINATION, route)
}
return {
- Log.d(TAG, "OpenPage: $route")
+ spaEnvironment.logger.message(
+ TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK
+ )
context.startActivity(intent)
}
}
@@ -244,7 +247,9 @@ open class DebugActivity : ComponentActivity() {
putExtra(KEY_HIGHLIGHT_ENTRY, entry.id)
}
return {
- Log.d(TAG, "OpenEntry: $route")
+ spaEnvironment.logger.message(
+ TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK
+ )
context.startActivity(intent)
}
}
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 7f2af9221c45..ca75b77dabc2 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
@@ -19,6 +19,9 @@ package com.android.settingslib.spa.framework.common
import android.os.Bundle
import android.widget.Toast
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ProvidedValue
+import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
@@ -28,6 +31,16 @@ import com.android.settingslib.spa.framework.compose.LocalNavController
const val INJECT_ENTRY_NAME = "INJECT"
const val ROOT_ENTRY_NAME = "ROOT"
+interface EntryData {
+ val pageId: String
+ val entryId: String
+ val isHighlighted: Boolean
+ get() = false
+}
+
+val LocalEntryDataProvider =
+ compositionLocalOf<EntryData> { error("LocalEntryDataProvider: No Default Value!") }
+
/**
* Defines data of a Settings entry.
*/
@@ -121,7 +134,22 @@ data class SettingsEntry(
// TODO: Add highlight entry logic
Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
}
- uiLayoutImpl(fullArgument(runtimeArguments))
+
+ CompositionLocalProvider(provideLocalEntryData()) {
+ uiLayoutImpl(fullArgument(runtimeArguments))
+ }
+ }
+
+ @Composable
+ fun provideLocalEntryData(): ProvidedValue<EntryData> {
+ val controller = LocalNavController.current
+ return LocalEntryDataProvider provides remember {
+ object : EntryData {
+ override val pageId = containerPage().id
+ override val entryId = id
+ override val isHighlighted = controller.highlightEntryId == id
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index ed4004f2b39c..ea20233288b4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -55,7 +55,7 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
val entry = entryQueue.pop()
val page = entry.toPage
if (page == null || pageWithEntryMap.containsKey(page.id)) continue
- val spp = sppRepository.getProviderOrNull(page.name) ?: continue
+ val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue
val newEntries = spp.buildEntry(page.arguments)
pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
for (newEntry in newEntries) {
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 2659c10f8eda..e7d89066b357 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
@@ -26,16 +26,16 @@ import com.android.settingslib.spa.framework.util.normalize
* Defines data to identify a Settings page.
*/
data class SettingsPage(
- // The unique id of this page, which is computed by name + normalized(arguments)
+ // The unique id of this page, which is computed by sppName + normalized(arguments)
val id: String,
- // The name of the page, which is used to compute the unique id, and need to be stable.
- val name: String,
+ // The name of the page provider, who creates this page. It is used to compute the unique id.
+ val sppName: String,
// The display name of the page, for better readability.
val displayName: String,
- // Defined parameters of this page.
+ // The parameters defined in its page provider.
val parameter: List<NamedNavArgument> = emptyList(),
// The arguments of this page.
@@ -50,7 +50,7 @@ data class SettingsPage(
): SettingsPage {
return SettingsPage(
id = id(name, parameter, arguments),
- name = name,
+ sppName = name,
displayName = displayName ?: name,
parameter = parameter,
arguments = arguments
@@ -70,7 +70,7 @@ data class SettingsPage(
// Returns if this Settings Page is created by the given Spp.
fun isCreateBy(SppName: String): Boolean {
- return name == SppName
+ return sppName == SppName
}
fun formatArguments(): String {
@@ -84,7 +84,7 @@ data class SettingsPage(
}
fun buildRoute(): String {
- return name + parameter.navLink(arguments)
+ return sppName + parameter.navLink(arguments)
}
fun hasRuntimeParam(): Boolean {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 965c2b39f4db..e8a44119ba84 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -21,13 +21,17 @@ import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
/**
- * An SettingsPageProvider represent a Settings page.
+ * An SettingsPageProvider which is used to create Settings page instances.
*/
interface SettingsPageProvider {
- /** The page name without arguments. */
+ /** The page provider name, needs to be *unique* and *stable*. */
val name: String
+ /** The display name of this page provider, for better readability. */
+ val displayName: String?
+ get() = null
+
/** The page parameters, default is no parameters. */
val parameter: List<NamedNavArgument>
get() = emptyList()
@@ -38,3 +42,12 @@ interface SettingsPageProvider {
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
}
+
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage.create(
+ name = name,
+ displayName = displayName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 3885025652f5..5baee4fb4acc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,21 +17,24 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
+import android.util.Log
+
+private const val TAG = "SpaEnvironment"
object SpaEnvironmentFactory {
private var spaEnvironment: SpaEnvironment? = null
- var instance: SpaEnvironment
+ fun reset(env: SpaEnvironment) {
+ spaEnvironment = env
+ Log.d(TAG, "reset")
+ }
+
+ val instance: SpaEnvironment
get() {
if (spaEnvironment == null)
throw UnsupportedOperationException("Spa environment is not set")
return spaEnvironment!!
}
- set(env: SpaEnvironment) {
- if (spaEnvironment != null)
- throw UnsupportedOperationException("Spa environment is already set")
- spaEnvironment = env
- }
}
abstract class SpaEnvironment {
@@ -43,5 +46,7 @@ abstract class SpaEnvironment {
open val entryProviderAuthorities: String? = null
+ open val logger: SpaLogger = object : SpaLogger {}
+
// TODO: add other environment setup here.
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
new file mode 100644
index 000000000000..5efedecae8d7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.util.Log
+
+// Defines the category of the log, for quick filter
+enum class LogCategory {
+ // The default category, for logs from Pages & their Models.
+ DEFAULT,
+
+ // For logs from Spa Framework, such as BrowseActivity, EntryProvider
+ FRAMEWORK,
+
+ // For logs from Spa UI components, such as Widgets, Scaffold
+ VIEW,
+}
+
+/**
+ * The interface of logger in Spa
+ */
+interface SpaLogger {
+ // log a message, usually for debug purpose.
+ fun message(tag: String, msg: String, category: LogCategory = LogCategory.DEFAULT) {}
+
+ // log a user event.
+ fun event(id: String, event: String, category: LogCategory = LogCategory.DEFAULT) {}
+}
+
+class LocalLogger : SpaLogger {
+ override fun message(tag: String, msg: String, category: LogCategory) {
+ Log.d("SpaMsg-$category", "[$tag] $msg")
+ }
+
+ override fun event(id: String, event: String, category: LogCategory) {
+ Log.d("SpaEvent-$category", "[$id] $event")
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/TEST_MAPPING b/packages/SettingsLib/SpaPrivileged/TEST_MAPPING
new file mode 100644
index 000000000000..ea16682863da
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "SpaPrivilegedLibTests"
+ },
+ {
+ "name": "SettingsSpaUnitTests"
+ }
+ ]
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 93ba4f7824b7..71cf23c61fa1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -24,7 +24,7 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.map
class AppOpsController(
context: Context,
@@ -36,7 +36,7 @@ class AppOpsController(
val mode: LiveData<Int>
get() = _mode
val isAllowed: LiveData<Boolean>
- get() = Transformations.map(_mode) { it == MODE_ALLOWED }
+ get() = _mode.map { it == MODE_ALLOWED }
fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 061580702327..b1adc9d27a88 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -20,7 +20,7 @@ import android.app.admin.DevicePolicyResources.Strings.Settings
import android.content.Context
import android.os.UserHandle
import android.os.UserManager
-import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.settingslib.spaprivileged.R
@@ -58,13 +58,8 @@ class RestrictionsProvider(
private val userManager by lazy { UserManager.get(context) }
private val enterpriseRepository by lazy { EnterpriseRepository(context) }
- val restrictedMode = object : LiveData<RestrictedMode>() {
- override fun onActive() {
- postValue(getRestrictedMode())
- }
-
- override fun onInactive() {
- }
+ val restrictedMode = liveData {
+ emit(getRestrictedMode())
}
private fun getRestrictedMode(): RestrictedMode {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 940a1fed817b..a1222a1af3c4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -41,6 +41,6 @@ android_test {
],
kotlincflags: [
"-Xjvm-default=all",
- "-Xopt-in=kotlin.RequiresOptIn",
+ "-opt-in=kotlin.RequiresOptIn",
],
}
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 6940c39d6617..33c2e4855b64 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -13,47 +13,62 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+
+<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:baselineAligned="false"
- android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/user_info_scroll"
android:padding="16dp">
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center">
- <ImageView
- android:id="@+id/user_photo"
- android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
- android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
- android:contentDescription="@string/user_image_photo_selector"
- android:scaleType="fitCenter"/>
- <ImageView
- android:id="@+id/add_a_photo_icon"
- android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
- android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
- android:src="@drawable/add_a_photo_circled"
- android:layout_gravity="bottom|right" />
- </FrameLayout>
-
- <EditText
- android:id="@+id/user_name"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="@dimen/user_name_height_in_user_info_dialog"
- android:layout_gravity="center"
- android:minWidth="200dp"
- android:layout_marginStart="6dp"
- android:minHeight="@dimen/min_tap_target_size"
- android:ellipsize="end"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textAlignment="viewStart"
- android:inputType="text|textCapWords"
- android:selectAllOnFocus="true"
- android:hint="@string/user_nickname"
- android:maxLength="100"/>
-
-</LinearLayout>
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/user_info_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/EditUserDialogTitle"
+ android:text="@string/user_info_settings_title"
+ android:textDirection="locale"/>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <ImageView
+ android:id="@+id/user_photo"
+ android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
+ android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
+ android:contentDescription="@string/user_image_photo_selector"
+ android:scaleType="fitCenter"/>
+ <ImageView
+ android:id="@+id/add_a_photo_icon"
+ android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+ android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+ android:src="@drawable/add_a_photo_circled"
+ android:layout_gravity="bottom|right"/>
+ </FrameLayout>
+
+ <EditText
+ android:id="@+id/user_name"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+ android:layout_gravity="center"
+ android:minWidth="200dp"
+ android:layout_marginStart="6dp"
+ android:minHeight="@dimen/min_tap_target_size"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAlignment="viewStart"
+ android:inputType="text|textCapWords"
+ android:selectAllOnFocus="true"
+ android:hint="@string/user_nickname"
+ android:maxLength="100"/>
+
+ </LinearLayout>
+
+</ScrollView>
+
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 5237b4fa1c3d..5a9e780df8e9 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -77,4 +77,10 @@
<item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
</style>
+ <style name="EditUserDialogTitle" parent="@android:TextAppearance.DeviceDefault.Headline">
+ <item name="android:textSize">@dimen/broadcast_dialog_title_text_size</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:ellipsize">end</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 3b542ccea635..e55d7eac34df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -31,6 +31,7 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
+import android.widget.ScrollView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -159,7 +160,8 @@ public class EditUserInfoController {
userPhotoView);
}
}
-
+ ScrollView scrollView = content.findViewById(R.id.user_info_scroll);
+ scrollView.setClipToOutline(true);
mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
defaultUserName, title, successCallback, cancelCallback);
@@ -182,7 +184,6 @@ public class EditUserInfoController {
@Nullable Drawable oldUserIcon, String defaultUserName, String title,
BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
return new AlertDialog.Builder(activity)
- .setTitle(title)
.setView(content)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index c5bea2ea6791..79677eeac429 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -4,6 +4,7 @@
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application android:allowClearUserData="false"
android:label="@string/app_label"
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index fab85b738906..def7ddc86556 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -213,7 +213,6 @@ public class SecureSettings {
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
Settings.Secure.WEAR_TALKBACK_ENABLED,
Settings.Secure.HBM_SETTING_KEY,
- Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED,
Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1454239caf82..cde4bc42b225 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -346,7 +346,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c3b645e7bc0d..4e2bce226d6c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1825,9 +1825,6 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
- dumpSetting(s, p,
- Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
- SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
p.end(accessibilityToken);
final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8efec671e582..3a25d85b5ecf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -86,6 +86,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.SystemConfigManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -110,12 +111,11 @@ import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FrameworkStatsLog;
import com.android.providers.settings.SettingsState.Setting;
-import com.android.server.SystemConfig;
-
-import com.google.android.collect.Sets;
import libcore.util.HexEncoding;
+import com.google.android.collect.Sets;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -368,6 +368,8 @@ public class SettingsProvider extends ContentProvider {
// We have to call in the package manager with no lock held,
private volatile IPackageManager mPackageManager;
+ private volatile SystemConfigManager mSysConfigManager;
+
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
@@ -397,6 +399,7 @@ public class SettingsProvider extends ContentProvider {
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
+ mSysConfigManager = getContext().getSystemService(SystemConfigManager.class);
mHandlerThread = new HandlerThread(LOG_TAG,
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
@@ -3875,8 +3878,7 @@ public class SettingsProvider extends ContentProvider {
Setting currentSetting = secureSettings.getSettingLocked(
Settings.Secure.ENABLED_VR_LISTENERS);
if (currentSetting.isNull()) {
- ArraySet<ComponentName> l =
- SystemConfig.getInstance().getDefaultVrComponents();
+ List<ComponentName> l = mSysConfigManager.getDefaultVrComponents();
if (l != null && !l.isEmpty()) {
StringBuilder b = new StringBuilder();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 528af2ec2528..765ee89229f3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -48,6 +48,7 @@ import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -97,7 +98,7 @@ final class SettingsState {
private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
- public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
+ public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 40000;
public static final int VERSION_UNDEFINED = -1;
@@ -376,8 +377,8 @@ final class SettingsState {
Setting newSetting = new Setting(name, oldSetting.getValue(), null,
oldSetting.getPackageName(), oldSetting.getTag(), false,
oldSetting.getId());
- int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
- newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+ int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), 0,
+ oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
mSettings.put(name, newSetting);
updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
@@ -414,8 +415,9 @@ final class SettingsState {
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
String newDefaultValue = makeDefault ? value : oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
- oldDefaultValue, newDefaultValue);
+ int newSize = getNewMemoryUsagePerPackageLocked(packageName,
+ oldValue == null ? name.length() : 0 /* deltaKeySize */,
+ oldValue, value, oldDefaultValue, newDefaultValue);
checkNewMemoryUsagePerPackageLocked(packageName, newSize);
Setting newState;
@@ -559,8 +561,12 @@ final class SettingsState {
}
Setting oldState = mSettings.remove(name);
- int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
- null, oldState.defaultValue, null);
+ if (oldState == null) {
+ return false;
+ }
+ int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName,
+ -name.length() /* deltaKeySize */,
+ oldState.value, null, oldState.defaultValue, null);
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
/* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
@@ -583,15 +589,16 @@ final class SettingsState {
}
Setting setting = mSettings.get(name);
+ if (setting == null) {
+ return false;
+ }
Setting oldSetting = new Setting(setting);
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
- String newValue = oldDefaultValue;
- String newDefaultValue = oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
- newValue, oldDefaultValue, newDefaultValue);
+ int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, 0, oldValue,
+ oldDefaultValue, oldDefaultValue, oldDefaultValue);
checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
if (!setting.reset()) {
@@ -725,19 +732,19 @@ final class SettingsState {
}
@GuardedBy("mLock")
- private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
- String newValue, String oldDefaultValue, String newDefaultValue) {
+ private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeyLength,
+ String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) {
if (isExemptFromMemoryUsageCap(packageName)) {
return 0;
}
- final Integer currentSize = mPackageToMemoryUsage.get(packageName);
- final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
- final int newValueSize = (newValue != null) ? newValue.length() : 0;
- final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
- final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
- final int deltaSize = newValueSize + newDefaultValueSize
- - oldValueSize - oldDefaultValueSize;
- return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+ final int currentSize = mPackageToMemoryUsage.getOrDefault(packageName, 0);
+ final int oldValueLength = (oldValue != null) ? oldValue.length() : 0;
+ final int newValueLength = (newValue != null) ? newValue.length() : 0;
+ final int oldDefaultValueLength = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
+ final int newDefaultValueLength = (newDefaultValue != null) ? newDefaultValue.length() : 0;
+ final int deltaSize = (deltaKeyLength + newValueLength + newDefaultValueLength
+ - oldValueLength - oldDefaultValueLength) * Character.BYTES;
+ return Math.max(currentSize + deltaSize, 0);
}
@GuardedBy("mLock")
@@ -1577,4 +1584,11 @@ final class SettingsState {
}
return false;
}
+
+ @VisibleForTesting
+ public int getMemoryUsage(String packageName) {
+ synchronized (mLock) {
+ return mPackageToMemoryUsage.getOrDefault(packageName, 0);
+ }
+ }
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index a637efa64ed0..4ed28d522678 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -294,7 +294,7 @@ public class SettingsStateTest extends AndroidTestCase {
settingsState.deleteSettingLocked(SETTING_NAME);
// Should not throw if usage is under the cap
- settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19975),
null, false, "p1");
settingsState.deleteSettingLocked(SETTING_NAME);
try {
@@ -312,5 +312,99 @@ public class SettingsStateTest extends AndroidTestCase {
assertTrue(ex.getMessage().contains("p1"));
}
assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+ try {
+ settingsState.insertSettingLocked(Strings.repeat("A", 20001), "",
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ }
+
+ public void testMemoryUsagePerPackage() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+
+ // Test inserting one key with default
+ final String testKey1 = SETTING_NAME;
+ final String testValue1 = Strings.repeat("A", 100);
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, TEST_PACKAGE);
+ int expectedMemUsage = (testKey1.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test inserting another key
+ final String testKey2 = SETTING_NAME + "2";
+ settingsState.insertSettingLocked(testKey2, testValue1, null, false, TEST_PACKAGE);
+ expectedMemUsage += (testKey2.length() + testValue1.length()) * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating first key with new default
+ final String testValue2 = Strings.repeat("A", 300);
+ settingsState.insertSettingLocked(testKey1, testValue2, null, true, TEST_PACKAGE);
+ expectedMemUsage += (testValue2.length() - testValue1.length()) * 2 * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating first key without new default
+ final String testValue3 = Strings.repeat("A", 50);
+ settingsState.insertSettingLocked(testKey1, testValue3, null, false, TEST_PACKAGE);
+ expectedMemUsage -= (testValue2.length() - testValue3.length()) * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test updating second key
+ settingsState.insertSettingLocked(testKey2, testValue2, null, false, TEST_PACKAGE);
+ expectedMemUsage -= (testValue1.length() - testValue2.length()) * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test resetting key
+ settingsState.resetSettingLocked(testKey1);
+ expectedMemUsage += (testValue2.length() - testValue3.length()) * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test resetting default value
+ settingsState.resetSettingDefaultValueLocked(testKey1);
+ expectedMemUsage -= testValue2.length() * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test deletion
+ settingsState.deleteSettingLocked(testKey2);
+ expectedMemUsage -= (testValue2.length() + testKey2.length() /* key is deleted too */)
+ * Character.BYTES;
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test another package with a different key
+ final String testPackage2 = TEST_PACKAGE + "2";
+ final String testKey3 = SETTING_NAME + "3";
+ settingsState.insertSettingLocked(testKey3, testValue1, null, true, testPackage2);
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ final int expectedMemUsage2 = (testKey3.length() + testValue1.length() * 2)
+ * Character.BYTES;
+ assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
+
+ // Test system package
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, SYSTEM_PACKAGE);
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
+ assertEquals(0, settingsState.getMemoryUsage(SYSTEM_PACKAGE));
+
+ // Test invalid value
+ try {
+ settingsState.insertSettingLocked(testKey1, Strings.repeat("A", 20001), null, false,
+ TEST_PACKAGE);
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+
+ // Test invalid key
+ try {
+ settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", null, false,
+ TEST_PACKAGE);
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("You are adding too many system settings"));
+ }
+ assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ddfac36e36b5..1a2ac833e05e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -141,6 +141,7 @@
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.LOCATION_BYPASS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 947691206741..cb37c079d2d0 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -199,8 +199,8 @@ public class BugreportReceiverTest {
}
mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2));
return null;
- }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), any(),
- anyBoolean());
+ }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
+ any(), anyBoolean());
setWarningState(mContext, STATE_HIDE);
@@ -543,7 +543,7 @@ public class BugreportReceiverTest {
getInstrumentation().waitForIdleSync();
verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), any(), anyBoolean());
+ anyInt(), anyInt(), any(), anyBoolean());
sendBugreportFinished();
}
@@ -608,7 +608,7 @@ public class BugreportReceiverTest {
ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass(
IDumpstateListener.class);
verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), listenerCap.capture(), anyBoolean());
+ anyInt(), anyInt(), listenerCap.capture(), anyBoolean());
mIDumpstateListener = listenerCap.getValue();
assertNotNull("Dumpstate listener should not be null", mIDumpstateListener);
mIDumpstateListener.onProgress(0);
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
new file mode 100644
index 000000000000..1d808ba7ee16
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.SdkConstants.CLASS_CONTEXT
+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 com.intellij.psi.PsiModifierListOwner
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.getParentOfType
+
+/**
+ * Warns if {@code Context.bindService}, {@code Context.bindServiceAsUser}, or {@code
+ * Context.unbindService} is not called on a {@code WorkerThread}
+ */
+@Suppress("UnstableApiUsage")
+class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("bindService", "bindServiceAsUser", "unbindService")
+ }
+
+ private fun hasWorkerThreadAnnotation(
+ context: JavaContext,
+ annotated: PsiModifierListOwner?
+ ): Boolean {
+ return context.evaluator.getAnnotations(annotated, inHierarchy = true).any { uAnnotation ->
+ uAnnotation.qualifiedName == "androidx.annotation.WorkerThread"
+ }
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
+ if (
+ !hasWorkerThreadAnnotation(context, node.getParentOfType(UMethod::class.java)) &&
+ !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
+ ) {
+ context.report(
+ ISSUE,
+ method,
+ context.getLocation(node),
+ "This method should be annotated with `@WorkerThread` because " +
+ "it calls ${method.name}",
+ )
+ }
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "BindServiceOnMainThread",
+ briefDescription = "Service bound or unbound on main thread",
+ explanation =
+ """
+ Binding and unbinding services are synchronous calls to `ActivityManager`. \
+ They usually take multiple milliseconds to complete. If called on the main \
+ thread, it will likely cause missed frames. To fix it, use a `@Background \
+ Executor` and annotate the calling method with `@WorkerThread`.
+ """,
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ BindServiceOnMainThreadDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 8d48f0957be4..112992913661 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -16,6 +16,7 @@
package com.android.internal.systemui.lint
+import com.android.SdkConstants.CLASS_CONTEXT
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -48,14 +49,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner {
return
}
- val evaulator = context.evaluator
- if (evaulator.isMemberInSubClassOf(method, "android.content.Context")) {
+ val evaluator = context.evaluator
+ if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
ISSUE,
method,
context.getNameLocation(node),
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ "`Context.${method.name}()` should be replaced with " +
+ "`BroadcastSender.${method.name}()`"
)
}
}
@@ -65,14 +66,14 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner {
val ISSUE: Issue =
Issue.create(
id = "BroadcastSentViaContext",
- briefDescription = "Broadcast sent via Context instead of BroadcastSender.",
- explanation =
- "Broadcast was sent via " +
- "Context.sendBroadcast/Context.sendBroadcastAsUser. Please use " +
- "BroadcastSender.sendBroadcast/BroadcastSender.sendBroadcastAsUser " +
- "which will schedule dispatch of broadcasts on background thread. " +
- "Sending broadcasts on main thread causes jank due to synchronous " +
- "Binder calls.",
+ briefDescription = "Broadcast sent via `Context` instead of `BroadcastSender`",
+ // lint trims indents and converts \ to line continuations
+ explanation = """
+ Broadcasts sent via `Context.sendBroadcast()` or \
+ `Context.sendBroadcastAsUser()` will block the main thread and may cause \
+ missed frames. Instead, use `BroadcastSender.sendBroadcast()` or \
+ `BroadcastSender.sendBroadcastAsUser()` which will schedule and dispatch \
+ broadcasts on a background worker thread.""",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
deleted file mode 100644
index a629eeeb0102..000000000000
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
+++ /dev/null
@@ -1,66 +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.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
-
-@Suppress("UnstableApiUsage")
-class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner {
-
- override fun getApplicableMethodNames(): List<String> {
- return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
- }
-
- override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
- if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
- context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Please inject a @Main Executor instead."
- )
- }
- }
-
- companion object {
- @JvmField
- val ISSUE: Issue =
- Issue.create(
- id = "GetMainLooperViaContextDetector",
- briefDescription = "Please use idiomatic SystemUI executors, injecting " +
- "them via Dagger.",
- explanation = "Injecting the @Main Executor is preferred in order to make" +
- "dependencies explicit and increase testability. It's much " +
- "easier to pass a FakeExecutor on your test ctor than to " +
- "deal with loopers in unit tests.",
- category = Category.LINT,
- priority = 8,
- severity = Severity.WARNING,
- implementation = Implementation(GetMainLooperViaContextDetector::class.java,
- Scope.JAVA_FILE_SCOPE)
- )
- }
-}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index 925fae0ebfb4..bab76ab4bce2 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -16,6 +16,7 @@
package com.android.internal.systemui.lint
+import com.android.SdkConstants.CLASS_CONTEXT
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -28,20 +29,19 @@ import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
@Suppress("UnstableApiUsage")
-class BindServiceViaContextDetector : Detector(), SourceCodeScanner {
+class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
- return listOf("bindService", "bindServiceAsUser", "unbindService")
+ return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
}
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
- if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Binding or unbinding services are synchronous calls, please make " +
- "sure you're on a @Background Executor."
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Replace with injected `@Main Executor`."
)
}
}
@@ -50,18 +50,20 @@ class BindServiceViaContextDetector : Detector(), SourceCodeScanner {
@JvmField
val ISSUE: Issue =
Issue.create(
- id = "BindServiceViaContextDetector",
- briefDescription = "Service bound/unbound via Context, please make sure " +
- "you're on a background thread.",
+ id = "NonInjectedMainThread",
+ briefDescription = "Main thread usage without dependency injection",
explanation =
- "Binding or unbinding services are synchronous calls to ActivityManager, " +
- "they usually take multiple milliseconds to complete and will make" +
- "the caller drop frames. Make sure you're on a @Background Executor.",
- category = Category.PERFORMANCE,
+ """
+ Main thread should be injected using the `@Main Executor` instead \
+ of using the accessors in `Context`. This is to make the \
+ dependencies explicit and increase testability. It's much easier \
+ to pass a `FakeExecutor` on test constructors than it is to deal \
+ with loopers in unit tests.""",
+ category = Category.LINT,
priority = 8,
severity = Severity.WARNING,
implementation =
- Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE)
+ Implementation(NonInjectedMainThreadDetector::class.java, Scope.JAVA_FILE_SCOPE)
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index 4eb7c7dd0d7e..b62290025437 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -16,6 +16,7 @@
package com.android.internal.systemui.lint
+import com.android.SdkConstants.CLASS_CONTEXT
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -32,7 +33,7 @@ import org.jetbrains.uast.UCallExpression
class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
- return listOf("getSystemService")
+ return listOf("getSystemService", "get")
}
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
@@ -40,14 +41,25 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
if (
!evaluator.isStatic(method) &&
method.name == "getSystemService" &&
- method.containingClass?.qualifiedName == "android.content.Context"
+ method.containingClass?.qualifiedName == CLASS_CONTEXT
) {
context.report(
ISSUE,
method,
context.getNameLocation(node),
- "Use @Inject to get the handle to a system-level services instead of using " +
- "Context.getSystemService()"
+ "Use `@Inject` to get system-level service handles instead of " +
+ "`Context.getSystemService()`"
+ )
+ } else if (
+ evaluator.isStatic(method) &&
+ method.name == "get" &&
+ method.containingClass?.qualifiedName == "android.accounts.AccountManager"
+ ) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
)
}
}
@@ -57,14 +69,14 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
val ISSUE: Issue =
Issue.create(
id = "NonInjectedService",
- briefDescription =
- "System-level services should be retrieved using " +
- "@Inject instead of Context.getSystemService().",
+ briefDescription = "System service not injected",
explanation =
- "Context.getSystemService() should be avoided because it makes testing " +
- "difficult. Instead, use an injected service. For example, " +
- "instead of calling Context.getSystemService(UserManager.class), " +
- "use @Inject and add UserManager to the constructor",
+ """
+ `Context.getSystemService()` should be avoided because it makes testing \
+ difficult. Instead, use an injected service. For example, instead of calling \
+ `Context.getSystemService(UserManager.class)` in a class, annotate the class' \
+ constructor with `@Inject` and add `UserManager` to the parameters.
+ """,
category = Category.CORRECTNESS,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index eb71d32b2d8b..4ba3afc7f7e2 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -16,6 +16,7 @@
package com.android.internal.systemui.lint
+import com.android.SdkConstants.CLASS_CONTEXT
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -35,12 +36,12 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
}
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
- if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
ISSUE,
method,
context.getNameLocation(node),
- "BroadcastReceivers should be registered via BroadcastDispatcher."
+ "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
)
}
}
@@ -49,14 +50,16 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
@JvmField
val ISSUE: Issue =
Issue.create(
- id = "RegisterReceiverViaContextDetector",
- briefDescription = "Broadcast registrations via Context are blocking " +
- "calls. Please use BroadcastDispatcher.",
- explanation =
- "Context#registerReceiver is a blocking call to the system server, " +
- "making it very likely that you'll drop a frame. Please use " +
- "BroadcastDispatcher instead (or move this call to a " +
- "@Background Executor.)",
+ id = "RegisterReceiverViaContext",
+ briefDescription = "Blocking broadcast registration",
+ // lint trims indents and converts \ to line continuations
+ explanation = """
+ `Context.registerReceiver()` is a blocking call to the system server, \
+ making it very likely that you'll drop a frame. Please use \
+ `BroadcastDispatcher` instead, which registers the receiver on a \
+ background thread. `BroadcastDispatcher` also improves our visibility \
+ into ANRs.""",
+ moreInfo = "go/identifying-broadcast-threads",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index b00661575c14..7be21a512f89 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -49,8 +49,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
ISSUE_SLOW_USER_ID_QUERY,
method,
context.getNameLocation(node),
- "ActivityManager.getCurrentUser() is slow. " +
- "Use UserTracker.getUserId() instead."
+ "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
)
}
if (
@@ -62,7 +61,7 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
ISSUE_SLOW_USER_INFO_QUERY,
method,
context.getNameLocation(node),
- "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead."
+ "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
)
}
}
@@ -72,11 +71,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
val ISSUE_SLOW_USER_ID_QUERY: Issue =
Issue.create(
id = "SlowUserIdQuery",
- briefDescription = "User ID queried using ActivityManager instead of UserTracker.",
+ briefDescription = "User ID queried using ActivityManager",
explanation =
- "ActivityManager.getCurrentUser() makes a binder call and is slow. " +
- "Instead, inject a UserTracker and call UserTracker.getUserId(). For " +
- "more info, see: http://go/multi-user-in-systemui-slides",
+ """
+ `ActivityManager.getCurrentUser()` uses a blocking binder call and is slow. \
+ Instead, inject a `UserTracker` and call `UserTracker.getUserId()`.
+ """,
+ moreInfo = "http://go/multi-user-in-systemui-slides",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
@@ -88,11 +89,13 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
val ISSUE_SLOW_USER_INFO_QUERY: Issue =
Issue.create(
id = "SlowUserInfoQuery",
- briefDescription = "User info queried using UserManager instead of UserTracker.",
+ briefDescription = "User info queried using UserManager",
explanation =
- "UserManager.getUserInfo() makes a binder call and is slow. " +
- "Instead, inject a UserTracker and call UserTracker.getUserInfo(). For " +
- "more info, see: http://go/multi-user-in-systemui-slides",
+ """
+ `UserManager.getUserInfo()` uses a blocking binder call and is slow. \
+ Instead, inject a `UserTracker` and call `UserTracker.getUserInfo()`.
+ """,
+ moreInfo = "http://go/multi-user-in-systemui-slides",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
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 a584894fed71..4eeeb850292a 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
@@ -47,7 +47,7 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
ISSUE,
referenced,
context.getNameLocation(referenced),
- "Usage of Config.HARDWARE is highly encouraged."
+ "Replace software bitmap with `Config.HARDWARE`"
)
}
}
@@ -56,12 +56,12 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
@JvmField
val ISSUE: Issue =
Issue.create(
- id = "SoftwareBitmapDetector",
- briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.",
- explanation =
- "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " +
- "In case you need to manipulate the pixels, please consider to either use" +
- "a shader (encouraged), or a short lived software bitmap.",
+ id = "SoftwareBitmap",
+ briefDescription = "Software bitmap",
+ explanation = """
+ Software bitmaps occupy twice as much memory as `Config.HARDWARE` bitmaps \
+ do. However, hardware bitmaps are read-only. If you need to manipulate the \
+ pixels, use a shader (preferably) or a short lived software bitmap.""",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
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 312810ba4633..cf7c1b5e44a2 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
@@ -28,11 +28,11 @@ class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(
- BindServiceViaContextDetector.ISSUE,
+ BindServiceOnMainThreadDetector.ISSUE,
BroadcastSentViaContextDetector.ISSUE,
SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
- GetMainLooperViaContextDetector.ISSUE,
+ NonInjectedMainThreadDetector.ISSUE,
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
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 26bd8d0a6ff4..486af9dd5d98 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
@@ -16,16 +16,21 @@
package com.android.internal.systemui.lint
+import com.android.annotations.NonNull
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import org.intellij.lang.annotations.Language
+
+@Suppress("UnstableApiUsage")
+@NonNull
+private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
* stubs are not used in the lint detectors themselves.
*/
-@Suppress("UnstableApiUsage")
internal val androidStubs =
arrayOf(
- java(
+ indentedJava(
"""
package android.app;
@@ -34,7 +39,16 @@ public class ActivityManager {
}
"""
),
- java(
+ indentedJava(
+ """
+package android.accounts;
+
+public class AccountManager {
+ public static AccountManager get(Context context) { return null; }
+}
+"""
+ ),
+ indentedJava(
"""
package android.os;
import android.content.pm.UserInfo;
@@ -45,39 +59,39 @@ public class UserManager {
}
"""
),
- java("""
+ indentedJava("""
package android.annotation;
public @interface UserIdInt {}
"""),
- java("""
+ indentedJava("""
package android.content.pm;
public class UserInfo {}
"""),
- java("""
+ indentedJava("""
package android.os;
public class Looper {}
"""),
- java("""
+ indentedJava("""
package android.os;
public class Handler {}
"""),
- java("""
+ indentedJava("""
package android.content;
public class ServiceConnection {}
"""),
- java("""
+ indentedJava("""
package android.os;
public enum UserHandle {
ALL
}
"""),
- java(
+ indentedJava(
"""
package android.content;
import android.os.UserHandle;
@@ -108,7 +122,7 @@ public class Context {
}
"""
),
- java(
+ indentedJava(
"""
package android.app;
import android.content.Context;
@@ -116,7 +130,7 @@ import android.content.Context;
public class Activity extends Context {}
"""
),
- java(
+ indentedJava(
"""
package android.graphics;
@@ -132,17 +146,17 @@ public class Bitmap {
}
"""
),
- java("""
+ indentedJava("""
package android.content;
public class BroadcastReceiver {}
"""),
- java("""
+ indentedJava("""
package android.content;
public class IntentFilter {}
"""),
- java(
+ indentedJava(
"""
package com.android.systemui.settings;
import android.content.pm.UserInfo;
@@ -153,4 +167,23 @@ 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
new file mode 100644
index 000000000000..6ae8fd3f25a1
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.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() {
+
+ override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
+
+ @Test
+ fun testBindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+
+ public class TestClass {
+ public void bind(Context context) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ context.bindService(intent, null, 0);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+ context.bindService(intent, null, 0);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testBindServiceAsUser() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserHandle;
+
+ public class TestClass {
+ public void bind(Context context) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+ context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testUnbindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls unbindService [BindServiceOnMainThread]
+ context.unbindService(connection);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testWorkerMethod() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+ import androidx.annotation.WorkerThread;
+
+ public class TestClass {
+ @WorkerThread
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+
+ public class ChildTestClass extends TestClass {
+ @Override
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testWorkerClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+ import androidx.annotation.WorkerThread;
+
+ @WorkerThread
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+
+ public class ChildTestClass extends TestClass {
+ @Override
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+
+ public void bind(Context context, ServiceConnection connection) {
+ context.bind(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
deleted file mode 100644
index 564afcb773fd..000000000000
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
+++ /dev/null
@@ -1,116 +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.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 BindServiceViaContextDetectorTest : LintDetectorTest() {
-
- override fun getDetector(): Detector = BindServiceViaContextDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
-
- override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE)
-
- private val explanation = "Binding or unbinding services are synchronous calls"
-
- @Test
- fun testBindService() {
- lint()
- .files(
- TestFiles.java(
- """
- package test.pkg;
- import android.content.Context;
-
- public class TestClass1 {
- public void bind(Context context) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- context.bindService(intent, null, 0);
- }
- }
- """
- )
- .indented(),
- *stubs
- )
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
- }
-
- @Test
- fun testBindServiceAsUser() {
- lint()
- .files(
- TestFiles.java(
- """
- package test.pkg;
- import android.content.Context;
- import android.os.UserHandle;
-
- public class TestClass1 {
- public void bind(Context context) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
- }
- }
- """
- )
- .indented(),
- *stubs
- )
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
- }
-
- @Test
- fun testUnbindService() {
- lint()
- .files(
- TestFiles.java(
- """
- package test.pkg;
- import android.content.Context;
- import android.content.ServiceConnection;
-
- public class TestClass1 {
- public void unbind(Context context, ServiceConnection connection) {
- context.unbindService(connection);
- }
- }
- """
- )
- .indented(),
- *stubs
- )
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
- }
-
- private val stubs = androidStubs
-}
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 06aee8e35898..7d422807ae08 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
@@ -41,7 +41,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
package test.pkg;
import android.content.Context;
- public class TestClass1 {
+ public class TestClass {
public void send(Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW);
context.sendBroadcast(intent);
@@ -54,10 +54,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Context.sendBroadcast() should be replaced with BroadcastSender.sendBroadcast() [BroadcastSentViaContext]
+ context.sendBroadcast(intent);
+ ~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
)
}
@@ -71,7 +74,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
import android.content.Context;
import android.os.UserHandle;
- public class TestClass1 {
+ public class TestClass {
public void send(Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW);
context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
@@ -84,10 +87,13 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ .expect(
+ """
+ src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext]
+ context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ ~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
)
}
@@ -101,7 +107,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
import android.app.Activity;
import android.os.UserHandle;
- public class TestClass1 {
+ public class TestClass {
public void send(Activity activity) {
Intent intent = new Intent(Intent.ACTION_VIEW);
activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
@@ -115,11 +121,41 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ .expect(
+ """
+ src/test/pkg/TestClass.java:8: Warning: Context.sendBroadcastAsUser() should be replaced with BroadcastSender.sendBroadcastAsUser() [BroadcastSentViaContext]
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ ~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testSendBroadcastInBroadcastSender() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package com.android.systemui.broadcast;
+ import android.app.Activity;
+ import android.os.UserHandle;
+
+ public class BroadcastSender {
+ public void send(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ }
+
+ }
+ """
+ )
+ .indented(),
+ *stubs
)
+ .issues(BroadcastSentViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
}
@Test
@@ -131,7 +167,7 @@ class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
package test.pkg;
import android.content.Context;
- public class TestClass1 {
+ public class TestClass {
public void sendBroadcast() {
Intent intent = new Intent(Intent.ACTION_VIEW);
context.startActivity(intent);
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index c55f3995f102..c468af8d09e0 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -24,14 +24,12 @@ import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
+class NonInjectedMainThreadDetectorTest : LintDetectorTest() {
- override fun getDetector(): Detector = GetMainLooperViaContextDetector()
+ override fun getDetector(): Detector = NonInjectedMainThreadDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
-
- private val explanation = "Please inject a @Main Executor instead."
+ override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE)
@Test
fun testGetMainThreadHandler() {
@@ -43,7 +41,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
import android.content.Context;
import android.os.Handler;
- public class TestClass1 {
+ public class TestClass {
public void test(Context context) {
Handler mainThreadHandler = context.getMainThreadHandler();
}
@@ -53,10 +51,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
.indented(),
*stubs
)
- .issues(GetMainLooperViaContextDetector.ISSUE)
+ .issues(NonInjectedMainThreadDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread]
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ ~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
@Test
@@ -69,7 +73,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
import android.content.Context;
import android.os.Looper;
- public class TestClass1 {
+ public class TestClass {
public void test(Context context) {
Looper mainLooper = context.getMainLooper();
}
@@ -79,10 +83,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
.indented(),
*stubs
)
- .issues(GetMainLooperViaContextDetector.ISSUE)
+ .issues(NonInjectedMainThreadDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread]
+ Looper mainLooper = context.getMainLooper();
+ ~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
@Test
@@ -95,7 +105,7 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
import android.content.Context;
import java.util.concurrent.Executor;
- public class TestClass1 {
+ public class TestClass {
public void test(Context context) {
Executor mainExecutor = context.getMainExecutor();
}
@@ -105,10 +115,16 @@ class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
.indented(),
*stubs
)
- .issues(GetMainLooperViaContextDetector.ISSUE)
+ .issues(NonInjectedMainThreadDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Replace with injected @Main Executor. [NonInjectedMainThread]
+ Executor mainExecutor = context.getMainExecutor();
+ ~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
private val stubs = androidStubs
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 6b9f88fedbdd..c83a35b46ca6 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
@@ -39,7 +39,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() {
package test.pkg;
import android.content.Context;
- public class TestClass1 {
+ public class TestClass {
public void getSystemServiceWithoutDagger(Context context) {
context.getSystemService("user");
}
@@ -51,8 +51,14 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() {
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains("Use @Inject to get the handle")
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService]
+ context.getSystemService("user");
+ ~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
@Test
@@ -65,7 +71,7 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() {
import android.content.Context;
import android.os.UserManager;
- public class TestClass2 {
+ public class TestClass {
public void getSystemServiceWithoutDagger(Context context) {
context.getSystemService(UserManager.class);
}
@@ -77,8 +83,46 @@ class NonInjectedServiceDetectorTest : LintDetectorTest() {
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains("Use @Inject to get the handle")
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Use @Inject to get system-level service handles instead of Context.getSystemService() [NonInjectedService]
+ context.getSystemService(UserManager.class);
+ ~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testGetAccountManager() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.accounts.AccountManager;
+
+ public class TestClass {
+ public void getSystemServiceWithoutDagger(Context context) {
+ AccountManager.get(context);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Replace AccountManager.get() with an injected instance of AccountManager [NonInjectedService]
+ AccountManager.get(context);
+ ~~~
+ 0 errors, 1 warnings
+ """
+ )
}
private val stubs = androidStubs
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 802ceba4196c..ebcddebfbc28 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
@@ -31,8 +31,6 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
- private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
-
@Test
fun testRegisterReceiver() {
lint()
@@ -44,7 +42,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
import android.content.Context;
import android.content.IntentFilter;
- public class TestClass1 {
+ public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
IntentFilter filter) {
context.registerReceiver(receiver, filter, 0);
@@ -57,8 +55,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ context.registerReceiver(receiver, filter, 0);
+ ~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
@Test
@@ -74,7 +78,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
import android.os.Handler;
import android.os.UserHandle;
- public class TestClass1 {
+ public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
IntentFilter filter, Handler handler) {
context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
@@ -88,8 +92,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
+ ~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
@Test
@@ -105,7 +115,7 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
import android.os.Handler;
import android.os.UserHandle;
- public class TestClass1 {
+ public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
IntentFilter filter, Handler handler) {
context.registerReceiverForAllUsers(receiver, filter, "permission",
@@ -119,8 +129,14 @@ class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ .expect(
+ """
+ src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ context.registerReceiverForAllUsers(receiver, filter, "permission",
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
}
private val stubs = androidStubs
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 e26583793e20..b03a11c4f02f 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
@@ -44,7 +44,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
package test.pkg;
import android.app.ActivityManager;
- public class TestClass1 {
+ public class TestClass {
public void slewlyGetCurrentUser() {
ActivityManager.getCurrentUser();
}
@@ -59,10 +59,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
)
.run()
- .expectWarningCount(1)
- .expectContains(
- "ActivityManager.getCurrentUser() is slow. " +
- "Use UserTracker.getUserId() instead."
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserId() instead of ActivityManager.getCurrentUser() [SlowUserIdQuery]
+ ActivityManager.getCurrentUser();
+ ~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
)
}
@@ -75,7 +78,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
package test.pkg;
import android.os.UserManager;
- public class TestClass2 {
+ public class TestClass {
public void slewlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
@@ -90,9 +93,13 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
)
.run()
- .expectWarningCount(1)
- .expectContains(
- "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead."
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Use UserTracker.getUserInfo() instead of UserManager.getUserInfo() [SlowUserInfoQuery]
+ userManager.getUserInfo();
+ ~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
)
}
@@ -105,7 +112,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
package test.pkg;
import com.android.systemui.settings.UserTracker;
- public class TestClass3 {
+ public class TestClass {
public void quicklyGetUserId(UserTracker userTracker) {
userTracker.getUserId();
}
@@ -132,7 +139,7 @@ class SlowUserQueryDetectorTest : LintDetectorTest() {
package test.pkg;
import com.android.systemui.settings.UserTracker;
- public class TestClass4 {
+ public class TestClass {
public void quicklyGetUserId(UserTracker userTracker) {
userTracker.getUserInfo();
}
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 fd6ab09a2ccd..fb6537e92d15 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
@@ -31,8 +31,6 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
- private val explanation = "Usage of Config.HARDWARE is highly encouraged."
-
@Test
fun testSoftwareBitmap() {
lint()
@@ -41,7 +39,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
"""
import android.graphics.Bitmap;
- public class TestClass1 {
+ public class TestClass {
public void test() {
Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
@@ -54,8 +52,17 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
- .expectWarningCount(2)
- .expectContains(explanation)
+ .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,
+ ~~~~~~~
+ 0 errors, 2 warnings
+ """
+ )
}
@Test
@@ -66,7 +73,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
"""
import android.graphics.Bitmap;
- public class TestClass1 {
+ public class TestClass {
public void test() {
Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE);
}
@@ -78,7 +85,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
- .expectWarningCount(0)
+ .expectClean()
}
private val stubs = androidStubs
diff --git a/packages/SystemUI/docs/device-entry/doze.md b/packages/SystemUI/docs/device-entry/doze.md
index 6b6dce5da169..10bd3679a13b 100644
--- a/packages/SystemUI/docs/device-entry/doze.md
+++ b/packages/SystemUI/docs/device-entry/doze.md
@@ -1,5 +1,7 @@
# Doze
+`Dozing` is a low-powered state of the device. If Always-on Display (AOD), pulsing, or wake-gestures are enabled, then the device will enter the `dozing` state after a user intent to turn off the screen (ie: power button) or the screen times out.
+
Always-on Display (AOD) provides an alternative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in. The recommended max on-pixel-ratio (OPR) is 5% to reduce battery consumption.
![ss-aod](./imgs/aod.png)
@@ -58,7 +60,7 @@ When Dozing is enabled, it can still be suppressed based on the device state. On
Refer to the documentation in [DozeSuppressors][15] for more information.
## AOD burn-in and image retention
-Because AOD will show an image on the screen for an elogated period of time, AOD designs must take into consideration burn-in (leaving a permanent mark on the screen). Temporary burn-in is called image-retention.
+Because AOD will show an image on the screen for an elongated period of time, AOD designs must take into consideration burn-in (leaving a permanent mark on the screen). Temporary burn-in is called image-retention.
To prevent burn-in, it is recommended to often shift UI on the screen. [DozeUi][17] schedules a call to dozeTimeTick every minute to request a shift in UI for all elements on AOD. The amount of shift can be determined by undergoing simulated AOD testing since this may vary depending on the display.
diff --git a/packages/SystemUI/docs/device-entry/glossary.md b/packages/SystemUI/docs/device-entry/glossary.md
index f3d12c21a3a5..7f19b1688de0 100644
--- a/packages/SystemUI/docs/device-entry/glossary.md
+++ b/packages/SystemUI/docs/device-entry/glossary.md
@@ -2,38 +2,38 @@
## Keyguard
-| Term | Description |
-| :-----------: | ----------- |
-| Keyguard, [keyguard.md][1] | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer.|
-| Lock screen<br><br>![ss_aod](imgs/lockscreen.png)| The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10].|
-| Bouncer, [bouncer.md][2]<br><br>![ss_aod](imgs/bouncer_pin.png)| The component responsible for displaying the primary security method set by the user (password, PIN, pattern). The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM.|
-| Split shade | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5]|
-| Ambient display (AOD), [doze.md][6]<br><br>![ss_aod](imgs/aod.png)| UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices.|
+| Term | Description |
+|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Keyguard, [keyguard.md][1] | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer. |
+| Lock screen<br><br>![ss_aod](imgs/lockscreen.png) | The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10]. |
+| Bouncer, [bouncer.md][2]<br><br>![ss_aod](imgs/bouncer_pin.png) | The component responsible for displaying the primary security method set by the user (password, PIN, pattern). The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM. |
+| Split shade | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5] |
+| Ambient display (AOD), [doze.md][6]<br><br>![ss_aod](imgs/aod.png) | UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices. |
## General Authentication Terms
-| Term | Description |
-| ----------- | ----------- |
-| Primary Authentication | The strongest form of authentication. Includes: Pin, pattern and password input.|
-| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7].|
+| Term | Description |
+|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
+| Primary Authentication | The strongest form of authentication. Includes: Pin, pattern and password input. |
+| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7]. |
## Face Authentication Terms
-| Term | Description |
-| ----------- | ----------- |
-| Passive Authentication | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry.|
-| Bypass | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above).|
-| Bypass User Journey <br><br>![ss_bypass](imgs/bypass.png)| Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app. This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen.|
-| Non-bypass User Journey | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility.|
+| Term | Description |
+|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Passive Authentication | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry. |
+| Bypass | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above). |
+| Bypass User Journey <br><br>![ss_bypass](imgs/bypass.png) | Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app. This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen. |
+| Non-bypass User Journey | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility. |
## Fingerprint Authentication Terms
-| Term | Description |
-| ----------- | ----------- |
-| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after two hard-fingerprint-failures, the primary authentication bouncer is shown</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul>|
-| UDFPS Bouncer | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade.|
+| Term | Description |
+|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after multiple consecutive hard-fingerprint-failures, the primary authentication bouncer is shown. The exact number of attempts is defined in: [BiometricUnlockController#UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER][4]</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul> |
+| UDFPS Bouncer | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade. |
## Other Authentication Terms
-| Term | Description |
-| ---------- | ----------- |
-| Trust Agents | Provides signals to the keyguard to allow it to lock less frequently.|
+| Term | Description |
+|--------------|-----------------------------------------------------------------------|
+| Trust Agents | Provides signals to the keyguard to allow it to lock less frequently. |
[1]: /frameworks/base/packages/SystemUI/docs/device-entry/keyguard.md
@@ -46,3 +46,4 @@
[8]: /frameworks/base/packages/SystemUI/res-keyguard/font/clock.xml
[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+[11]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index d90156d451c7..8135aaa6faea 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -241,4 +241,6 @@
<string name="clock_title_bubble">Bubble</string>
<!-- Name of the "Analog" clock face [CHAR LIMIT=15]-->
<string name="clock_title_analog">Analog</string>
+ <!-- Title of bouncer when we want to authenticate before continuing with action. [CHAR LIMIT=NONE] -->
+ <string name="keyguard_unlock_to_continue">Unlock your device to continue</string>
</resources>
diff --git a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
index 69390848245d..33c68bf1f6ac 100644
--- a/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
+++ b/packages/SystemUI/res/drawable/bg_smartspace_media_item.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="@android:color/white" />
+ <solid android:color="@android:color/transparent" />
<corners android:radius="@dimen/qs_media_album_radius" />
</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 1a1fc75a41a1..0e9abee2f050 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.screenshot.DraggableConstraintLayout
+<com.android.systemui.clipboardoverlay.ClipboardOverlayView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -157,4 +157,4 @@
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout> \ No newline at end of file
+</com.android.systemui.clipboardoverlay.ClipboardOverlayView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
new file mode 100644
index 000000000000..1a1fc75a41a1
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.systemui.screenshot.DraggableConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/clipboard_ui"
+ android:theme="@style/FloatingOverlay"
+ android:alpha="0"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/clipboard_overlay_window_name">
+ <ImageView
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/actions_container"
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:scrollbars="none"
+ android:layout_marginBottom="4dp"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintWidth_percent="1.0"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/preview_border"
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true">
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/share_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/remote_copy_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/edit_chip"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <View
+ android:id="@+id/preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="12dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:elevation="7dp"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+ android:background="@drawable/overlay_border"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="@dimen/overlay_border_width"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="top"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <FrameLayout
+ android:id="@+id/clipboard_preview"
+ android:elevation="7dp"
+ android:background="@drawable/overlay_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_margin="@dimen/overlay_border_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:layout_constraintBottom_toBottomOf="@id/preview_border"
+ app:layout_constraintStart_toStartOf="@id/preview_border"
+ app:layout_constraintEnd_toEndOf="@id/preview_border"
+ app:layout_constraintTop_toTopOf="@id/preview_border">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
+ android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:textColorLink="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <ImageView
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/clipboard_image_preview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/hidden_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="10dp"
+ android:visibility="gone"
+ android:alpha="0"
+ app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ android:contentDescription="@string/clipboard_dismiss_description">
+ <ImageView
+ android:id="@+id/dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index d7a0b473ead6..3efdc5acb00c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -17,6 +17,7 @@ package com.android.systemui.shared.animation
import android.graphics.Point
import android.view.Surface
+import android.view.Surface.Rotation
import android.view.View
import android.view.WindowManager
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -58,14 +59,14 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
* Updates display properties in order to calculate the initial position for the views
* Must be called before [registerViewForAnimation]
*/
- fun updateDisplayProperties() {
+ @JvmOverloads
+ fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) {
windowManager.defaultDisplay.getSize(screenSize)
// Simple implementation to get current fold orientation,
// this might not be correct on all devices
// TODO: use JetPack WindowManager library to get the fold orientation
- isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 ||
- windowManager.defaultDisplay.rotation == Surface.ROTATION_180
+ isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index ec938b219933..aca9907fec1b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -15,12 +15,11 @@
package com.android.systemui.unfold.util
import android.content.Context
-import android.os.RemoteException
-import android.view.IRotationWatcher
-import android.view.IWindowManager
import android.view.Surface
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
/**
* [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
@@ -29,27 +28,21 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr
*/
class NaturalRotationUnfoldProgressProvider(
private val context: Context,
- private val windowManagerInterface: IWindowManager,
+ private val rotationChangeProvider: RotationChangeProvider,
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
) : UnfoldTransitionProgressProvider {
private val scopedUnfoldTransitionProgressProvider =
ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
- private val rotationWatcher = RotationWatcher()
private var isNaturalRotation: Boolean = false
fun init() {
- try {
- windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
- } catch (e: RemoteException) {
- throw e.rethrowFromSystemServer()
- }
-
- onRotationChanged(context.display.rotation)
+ rotationChangeProvider.addCallback(rotationListener)
+ rotationListener.onRotationChanged(context.display.rotation)
}
- private fun onRotationChanged(rotation: Int) {
+ private val rotationListener = RotationListener { rotation ->
val isNewRotationNatural =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
@@ -60,12 +53,7 @@ class NaturalRotationUnfoldProgressProvider(
}
override fun destroy() {
- try {
- windowManagerInterface.removeRotationWatcher(rotationWatcher)
- } catch (e: RemoteException) {
- e.rethrowFromSystemServer()
- }
-
+ rotationChangeProvider.removeCallback(rotationListener)
scopedUnfoldTransitionProgressProvider.destroy()
}
@@ -76,10 +64,4 @@ class NaturalRotationUnfoldProgressProvider(
override fun removeCallback(listener: TransitionProgressListener) {
scopedUnfoldTransitionProgressProvider.removeCallback(listener)
}
-
- private inner class RotationWatcher : IRotationWatcher.Stub() {
- override fun onRotationChanged(rotation: Int) {
- this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation)
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index 692fe83ee2b8..e6a2bfa1af12 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -17,7 +17,6 @@
package com.android.keyguard
import android.app.StatusBarManager.SESSION_KEYGUARD
-import android.content.Context
import android.hardware.biometrics.BiometricSourceType
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.UiEvent
@@ -41,11 +40,10 @@ import javax.inject.Inject
*/
@SysUISingleton
class KeyguardBiometricLockoutLogger @Inject constructor(
- context: Context?,
private val uiEventLogger: UiEventLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val sessionTracker: SessionTracker
-) : CoreStartable(context) {
+) : CoreStartable {
private var fingerprintLockedOut = false
private var faceLockedOut = false
private var encryptedOrLockdown = false
@@ -169,4 +167,4 @@ class KeyguardBiometricLockoutLogger @Inject constructor(
return strongAuthFlags and flagCheck != 0
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c34db1532d6c..93ee151f26c5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -67,7 +67,6 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
-import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -318,7 +317,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
}
void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
- UserSwitcherController userSwitcherController) {
+ UserSwitcherController userSwitcherController,
+ UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) {
if (mCurrentMode == mode) return;
Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
+ modeToString(mode));
@@ -330,7 +330,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
mViewMode = new OneHandedViewMode();
break;
case MODE_USER_SWITCHER:
- mViewMode = new UserSwitcherViewMode();
+ mViewMode = new UserSwitcherViewMode(userSwitcherCallback);
break;
default:
mViewMode = new DefaultViewMode();
@@ -864,6 +864,12 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
+ private UserSwitcherCallback mUserSwitcherCallback;
+
+ UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
+ mUserSwitcherCallback = userSwitcherCallback;
+ }
+
@Override
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@@ -1040,34 +1046,25 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
}
};
- if (adapter.getCount() < 2) {
- // The drop down arrow is at index 1
- ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0);
- anchor.setClickable(false);
- return;
- } else {
- ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255);
- }
-
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
mPopup.setAnchorView(anchor);
mPopup.setAdapter(adapter);
- mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- public void onItemClick(AdapterView parent, View view, int pos, long id) {
- if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
- if (!view.isEnabled()) return;
-
- // Subtract one for the header
- UserRecord user = adapter.getItem(pos - 1);
- if (!user.isCurrent) {
- adapter.onUserListItemClicked(user);
- }
- mPopup.dismiss();
- mPopup = null;
- }
- });
+ mPopup.setOnItemClickListener((parent, view, pos, id) -> {
+ if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
+ if (!view.isEnabled()) return;
+ // Subtract one for the header
+ UserRecord user = adapter.getItem(pos - 1);
+ if (user.isManageUsers || user.isAddSupervisedUser) {
+ mUserSwitcherCallback.showUnlockToContinueMessage();
+ }
+ if (!user.isCurrent) {
+ adapter.onUserListItemClicked(user);
+ }
+ mPopup.dismiss();
+ mPopup = null;
+ });
mPopup.show();
});
}
@@ -1122,6 +1119,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
constraintSet.applyTo(mView);
}
}
+
+ interface UserSwitcherCallback {
+ void showUnlockToContinueMessage();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d448f40ed529..bcd1a1ee2696 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -620,7 +620,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mode = KeyguardSecurityContainer.MODE_ONE_HANDED;
}
- mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController);
+ mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController,
+ () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue),
+ null));
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 9eb2c118abac..c9128e5881cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -109,12 +109,13 @@ class KeyguardSecurityViewTransition : Transition() {
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
runningSecurityShiftAnimator = null
+ if (shouldRestoreLayerType) {
+ v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
+ }
}
}
)
- var finishedFadingOutNonSecurityView = false
-
runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
val isFadingOut = animation.animatedFraction < switchPoint
@@ -153,6 +154,13 @@ class KeyguardSecurityViewTransition : Transition() {
startRect.right + currentTranslation,
startRect.bottom
)
+ } else {
+ v.setLeftTopRightBottom(
+ startRect.left,
+ startRect.top,
+ startRect.right,
+ startRect.bottom
+ )
}
} else {
// And in again over the remaining (100-X)%.
@@ -175,32 +183,13 @@ class KeyguardSecurityViewTransition : Transition() {
endRect.right - translationRemaining,
endRect.bottom
)
- }
- }
- if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
- v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
- }
-
- // For views that are not the security view flipper, we do not want to apply
- // an x translation animation. Instead, we want to fade out, move to final position and
- // then fade in.
- if (v !is KeyguardSecurityViewFlipper) {
- // Opacity goes close to 0 but does not fully get to 0.
- if (opacity - 0.001f < 0f) {
+ } else {
v.setLeftTopRightBottom(
endRect.left,
endRect.top,
endRect.right,
endRect.bottom
)
- finishedFadingOutNonSecurityView = true
- } else if (!finishedFadingOutNonSecurityView) {
- v.setLeftTopRightBottom(
- startRect.left,
- startRect.top,
- startRect.right,
- startRect.bottom
- )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 37829f25d179..a89cbf57f95b 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -19,11 +19,11 @@ import kotlinx.coroutines.withContext
@SysUISingleton
class ChooserSelector @Inject constructor(
- context: Context,
+ private val context: Context,
private val featureFlags: FeatureFlags,
@Application private val coroutineScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher
-) : CoreStartable(context) {
+) : CoreStartable {
private val packageManager = context.packageManager
private val chooserComponent = ComponentName.unflattenFromString(
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 0201cdc25319..929ebea37eef 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,39 +16,41 @@
package com.android.systemui;
-import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.PrintWriter;
/**
- * A top-level module of system UI code (sometimes called "system UI services" elsewhere in code).
- * Which CoreStartable modules are loaded can be controlled via a config resource.
+ * Code that needs to be run when SystemUI is started.
+ *
+ * Which CoreStartable modules are loaded is controlled via the dagger graph. Bind them into the
+ * CoreStartable map with code such as:
+ *
+ * <pre>
+ * &#64;Binds
+ * &#64;IntoMap
+ * &#64;ClassKey(FoobarStartable::class)
+ * abstract fun bind(impl: FoobarStartable): CoreStartable
+ * </pre>
*
* @see SystemUIApplication#startServicesIfNeeded()
*/
-public abstract class CoreStartable implements Dumpable {
- protected final Context mContext;
-
- public CoreStartable(Context context) {
- mContext = context;
- }
+public interface CoreStartable extends Dumpable {
/** Main entry point for implementations. Called shortly after app startup. */
- public abstract void start();
+ void start();
- protected void onConfigurationChanged(Configuration newConfig) {
+ /** */
+ default void onConfigurationChanged(Configuration newConfig) {
}
@Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
- @VisibleForTesting
- protected void onBootCompleted() {
+ /** Called when the device reports BOOT_COMPLETED. */
+ default void onBootCompleted() {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 9cdce6400e56..8f419560c78d 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -46,7 +46,7 @@ import javax.inject.Inject;
* system that are used for testing the latency.
*/
@SysUISingleton
-public class LatencyTester extends CoreStartable {
+public class LatencyTester implements CoreStartable {
private static final boolean DEFAULT_ENABLED = Build.IS_ENG;
private static final String
ACTION_FINGERPRINT_WAKE =
@@ -62,13 +62,11 @@ public class LatencyTester extends CoreStartable {
@Inject
public LatencyTester(
- Context context,
BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher,
DeviceConfigProxy deviceConfigProxy,
@Main DelayableExecutor mainExecutor
) {
- super(context);
mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
mDeviceConfigProxy = deviceConfigProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 2e13903814a5..b5f42a164495 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -105,7 +105,7 @@ import kotlin.Pair;
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable {
+public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
private static final boolean DEBUG = false;
private static final String TAG = "ScreenDecorations";
@@ -130,6 +130,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@VisibleForTesting
protected boolean mIsRegistered;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Context mContext;
private final Executor mMainExecutor;
private final TunerService mTunerService;
private final SecureSettings mSecureSettings;
@@ -308,7 +309,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory) {
- super(context);
+ mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
mBroadcastDispatcher = broadcastDispatcher;
@@ -973,7 +974,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
Log.i(TAG, "ScreenDecorations is disabled");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index 1f2de4cfc346..5bd85a72b06f 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -38,16 +38,17 @@ import javax.inject.Inject;
* @see SliceBroadcastRelay
*/
@SysUISingleton
-public class SliceBroadcastRelayHandler extends CoreStartable {
+public class SliceBroadcastRelayHandler implements CoreStartable {
private static final String TAG = "SliceBroadcastRelay";
private static final boolean DEBUG = false;
private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
+ private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) {
- super(context);
+ mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9cfd3999a0ec..d9f44cdecf40 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -45,8 +45,6 @@ import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.NotificationChannels;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
@@ -287,14 +285,10 @@ public class SystemUIApplication extends Application implements
CoreStartable startable;
if (DEBUG) Log.d(TAG, "loading: " + clsName);
try {
- Constructor<?> constructor = Class.forName(clsName).getConstructor(
- Context.class);
- startable = (CoreStartable) constructor.newInstance(this);
+ startable = (CoreStartable) Class.forName(clsName).newInstance();
} catch (ClassNotFoundException
- | NoSuchMethodException
| IllegalAccessException
- | InstantiationException
- | InvocationTargetException ex) {
+ | InstantiationException ex) {
throw new RuntimeException(ex);
}
diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java
index 139448c0fab4..a3209396ac27 100644
--- a/packages/SystemUI/src/com/android/systemui/VendorServices.java
+++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java
@@ -16,15 +16,12 @@
package com.android.systemui;
-import android.content.Context;
-
/**
* Placeholder for any vendor-specific services.
*/
-public class VendorServices extends CoreStartable {
+public class VendorServices implements CoreStartable {
- public VendorServices(Context context) {
- super(context);
+ public VendorServices() {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index a1288b531955..9f1c9b45e6cd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -69,7 +69,7 @@ import dagger.Lazy;
* Class to register system actions with accessibility framework.
*/
@SysUISingleton
-public class SystemActions extends CoreStartable {
+public class SystemActions implements CoreStartable {
private static final String TAG = "SystemActions";
/**
@@ -177,6 +177,7 @@ public class SystemActions extends CoreStartable {
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final SystemActionsBroadcastReceiver mReceiver;
+ private final Context mContext;
private final Optional<Recents> mRecentsOptional;
private Locale mLocale;
private final AccessibilityManager mA11yManager;
@@ -190,7 +191,7 @@ public class SystemActions extends CoreStartable {
NotificationShadeWindowController notificationShadeController,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional) {
- super(context);
+ mContext = context;
mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -219,7 +220,6 @@ public class SystemActions extends CoreStartable {
@Override
public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
if (!locale.equals(mLocale)) {
mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index f4701ed50ccc..4f03b6316d99 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -53,11 +53,12 @@ import javax.inject.Inject;
* when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
*/
@SysUISingleton
-public class WindowMagnification extends CoreStartable implements WindowMagnifierCallback,
+public class WindowMagnification implements CoreStartable, WindowMagnifierCallback,
CommandQueue.Callbacks {
private static final String TAG = "WindowMagnification";
private final ModeSwitchesController mModeSwitchesController;
+ private final Context mContext;
private final Handler mHandler;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
@@ -108,7 +109,7 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie
public WindowMagnification(Context context, @Main Handler mainHandler,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService) {
- super(context);
+ mContext = context;
mHandler = mainHandler;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d1bc968a5be6..242a5983a59d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -104,7 +104,7 @@ import kotlin.Unit;
* {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
-public class AuthController extends CoreStartable implements CommandQueue.Callbacks,
+public class AuthController implements CoreStartable, CommandQueue.Callbacks,
AuthDialogCallback, DozeReceiver {
private static final String TAG = "AuthController";
@@ -112,6 +112,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
private static final int SENSOR_PRIVACY_DELAY = 500;
private final Handler mHandler;
+ private final Context mContext;
private final Execution mExecution;
private final CommandQueue mCommandQueue;
private final StatusBarStateController mStatusBarStateController;
@@ -697,7 +698,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibrator) {
- super(context);
+ mContext = context;
mExecution = execution;
mUserManager = userManager;
mLockPatternUtils = lockPatternUtils;
@@ -1152,8 +1153,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
updateSensorLocations();
// Save the state of the current dialog (buttons showing, etc)
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
index d7b263a323ca..c536e81ef20a 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt
@@ -16,16 +16,14 @@
package com.android.systemui.broadcast
-import android.content.Context
import com.android.systemui.CoreStartable
import javax.inject.Inject
class BroadcastDispatcherStartable @Inject constructor(
- context: Context,
val broadcastDispatcher: BroadcastDispatcher
-) : CoreStartable(context) {
+) : CoreStartable {
override fun start() {
broadcastDispatcher.initialize()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index f526277a0a37..82e570438dab 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -31,16 +31,19 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
*/
@SysUISingleton
-public class ClipboardListener extends CoreStartable
- implements ClipboardManager.OnPrimaryClipChangedListener {
+public class ClipboardListener implements
+ CoreStartable, ClipboardManager.OnPrimaryClipChangedListener {
private static final String TAG = "ClipboardListener";
@VisibleForTesting
@@ -49,21 +52,32 @@ public class ClipboardListener extends CoreStartable
static final String EXTRA_SUPPRESS_OVERLAY =
"com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
+ private final Context mContext;
private final DeviceConfigProxy mDeviceConfig;
- private final ClipboardOverlayControllerFactory mOverlayFactory;
+ private final Provider<ClipboardOverlayController> mOverlayProvider;
+ private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger;
- private ClipboardOverlayController mClipboardOverlayController;
+ private final FeatureFlags mFeatureFlags;
+ private boolean mUsingNewOverlay;
+ private ClipboardOverlay mClipboardOverlay;
@Inject
public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
- ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager,
- UiEventLogger uiEventLogger) {
- super(context);
+ Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
+ ClipboardOverlayControllerLegacyFactory overlayFactory,
+ ClipboardManager clipboardManager,
+ UiEventLogger uiEventLogger,
+ FeatureFlags featureFlags) {
+ mContext = context;
mDeviceConfig = deviceConfigProxy;
+ mOverlayProvider = clipboardOverlayControllerProvider;
mOverlayFactory = overlayFactory;
mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger;
+ mFeatureFlags = featureFlags;
+
+ mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
}
@Override
@@ -88,16 +102,22 @@ public class ClipboardListener extends CoreStartable
return;
}
- if (mClipboardOverlayController == null) {
- mClipboardOverlayController = mOverlayFactory.create(mContext);
+ boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
+ if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
+ mUsingNewOverlay = enabled;
+ if (enabled) {
+ mClipboardOverlay = mOverlayProvider.get();
+ } else {
+ mClipboardOverlay = mOverlayFactory.create(mContext);
+ }
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlayController.setClipData(clipData, clipSource);
- mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+ mClipboardOverlay.setClipData(clipData, clipSource);
+ mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
- mClipboardOverlayController = null;
+ mClipboardOverlay = null;
});
}
@@ -119,4 +139,10 @@ public class ClipboardListener extends CoreStartable
private static boolean isEmulator() {
return SystemProperties.getBoolean("ro.boot.qemu", false);
}
+
+ interface ClipboardOverlay {
+ void setClipData(ClipData clipData, String clipSource);
+
+ void setOnSessionCompleteListener(Runnable runnable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 7e499ebdf691..bfb27a4c87b8 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
package com.android.systemui.clipboardoverlay;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
@@ -37,11 +36,6 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
@@ -52,14 +46,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
@@ -67,57 +54,37 @@ import android.os.AsyncTask;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.MathUtils;
import android.util.Size;
-import android.util.TypedValue;
import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Optional;
+
+import javax.inject.Inject;
/**
* Controls state and UI for the overlay that appears when something is added to the clipboard
*/
-public class ClipboardOverlayController {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
private static final String TAG = "ClipboardOverlayCtrlr";
/** Constants for screenshot/copy deconflicting */
@@ -126,36 +93,22 @@ public class ClipboardOverlayController {
public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
- private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private static final int FONT_SEARCH_STEP_PX = 4;
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayManager mDisplayManager;
- private final DisplayMetrics mDisplayMetrics;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
- private final PhoneWindow mWindow;
+ private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
- private final AccessibilityManager mAccessibilityManager;
private final TextClassifier mTextClassifier;
- private final DraggableConstraintLayout mView;
- private final View mClipboardPreview;
- private final ImageView mImagePreview;
- private final TextView mTextPreview;
- private final TextView mHiddenPreview;
- private final View mPreviewBorder;
- private final OverlayActionChip mEditChip;
- private final OverlayActionChip mShareChip;
- private final OverlayActionChip mRemoteCopyChip;
- private final View mActionContainerBackground;
- private final View mDismissButton;
- private final LinearLayout mActionContainer;
- private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+ private final ClipboardOverlayView mView;
private Runnable mOnSessionCompleteListener;
+ private Runnable mOnRemoteCopyTapped;
+ private Runnable mOnShareTapped;
+ private Runnable mOnEditTapped;
+ private Runnable mOnPreviewTapped;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -163,14 +116,66 @@ public class ClipboardOverlayController {
private BroadcastReceiver mCloseDialogsReceiver;
private BroadcastReceiver mScreenshotReceiver;
- private boolean mBlockAttach = false;
private Animator mExitAnimator;
private Animator mEnterAnimator;
- private final int mOrientation;
- private boolean mKeyboardVisible;
+ private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
+ new ClipboardOverlayView.ClipboardOverlayCallbacks() {
+ @Override
+ public void onInteraction() {
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override
+ public void onSwipeDismissInitiated(Animator animator) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ mExitAnimator = animator;
+ }
+
+ @Override
+ public void onDismissComplete() {
+ hideImmediate();
+ }
+
+ @Override
+ public void onPreviewTapped() {
+ if (mOnPreviewTapped != null) {
+ mOnPreviewTapped.run();
+ }
+ }
+
+ @Override
+ public void onShareButtonTapped() {
+ if (mOnShareTapped != null) {
+ mOnShareTapped.run();
+ }
+ }
+
+ @Override
+ public void onEditButtonTapped() {
+ if (mOnEditTapped != null) {
+ mOnEditTapped.run();
+ }
+ }
+
+ @Override
+ public void onRemoteCopyButtonTapped() {
+ if (mOnRemoteCopyTapped != null) {
+ mOnRemoteCopyTapped.run();
+ }
+ }
+
+ @Override
+ public void onDismissButtonTapped() {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ animateOut();
+ }
+ };
- public ClipboardOverlayController(Context context,
+ @Inject
+ public ClipboardOverlayController(@OverlayWindowContext Context context,
+ ClipboardOverlayView clipboardOverlayView,
+ ClipboardOverlayWindow clipboardOverlayWindow,
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
@@ -181,121 +186,26 @@ public class ClipboardOverlayController {
mClipboardLogger = new ClipboardLogger(uiEventLogger);
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mView = clipboardOverlayView;
+ mWindow = clipboardOverlayWindow;
+ mWindow.init(mView::setInsets, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ hideImmediate();
+ });
+
mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
.getTextClassifier();
- mWindowManager = mContext.getSystemService(WindowManager.class);
-
- mDisplayMetrics = new DisplayMetrics();
- mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
- // Setup the window that we are going to use
- mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
- mWindowLayoutParams.setTitle("ClipboardOverlay");
-
- mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
- mWindow.setWindowManager(mWindowManager, null, null);
-
- setWindowFocusable(false);
-
- mView = (DraggableConstraintLayout)
- LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
- mActionContainerBackground =
- requireNonNull(mView.findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
- mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
- mEditChip.setAlpha(1);
- mShareChip.setAlpha(1);
- mRemoteCopyChip.setAlpha(1);
- mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
- mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
- mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
- @Override
- public void onInteraction() {
- mTimeoutHandler.resetTimeout();
- }
-
- @Override
- public void onSwipeDismissInitiated(Animator animator) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
- mExitAnimator = animator;
- }
+ mView.setCallbacks(mClipboardCallbacks);
- @Override
- public void onDismissComplete() {
- hideImmediate();
- }
- });
- mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
- int availableHeight = mTextPreview.getHeight()
- - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
- mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
- return true;
- });
-
- mDismissButton.setOnClickListener(view -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
- animateOut();
- });
-
- mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
- mRemoteCopyChip.setIcon(
- Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
- mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
- mOrientation = mContext.getResources().getConfiguration().orientation;
-
- attachWindow();
- withWindowAttached(() -> {
+ mWindow.withWindowAttached(() -> {
mWindow.setContentView(mView);
- WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- updateInsets(insets);
- mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- WindowInsets insets =
- mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- if (keyboardVisible != mKeyboardVisible) {
- mKeyboardVisible = keyboardVisible;
- updateInsets(insets);
- }
- }
- });
- mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
- new ViewRootImpl.ActivityConfigCallback() {
- @Override
- public void onConfigurationChanged(Configuration overrideConfig,
- int newDisplayId) {
- if (mContext.getResources().getConfiguration().orientation
- != mOrientation) {
- mClipboardLogger.logSessionComplete(
- CLIPBOARD_OVERLAY_DISMISSED_OTHER);
- hideImmediate();
- }
- }
-
- @Override
- public void requestCompatCameraControl(
- boolean showControl, boolean transformationApplied,
- ICompatCameraControlCallback callback) {
- Log.w(TAG, "unexpected requestCompatCameraControl call");
- }
- });
+ mView.setInsets(mWindow.getWindowInsets(),
+ mContext.getResources().getConfiguration().orientation);
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
@@ -336,21 +246,19 @@ public class ClipboardOverlayController {
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
- void setClipData(ClipData clipData, String clipSource) {
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipData(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
reset();
- String accessibilityAnnouncement;
+ String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
&& clipData.getDescription().getExtras()
.getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ mView.showDefaultTextPreview();
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -360,53 +268,47 @@ public class ClipboardOverlayController {
}
}
if (isSensitive) {
- showEditableText(
- mContext.getResources().getString(R.string.clipboard_asterisks), true);
+ showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
} else {
showEditableText(item.getText(), false);
}
- showShareChip(clipData);
+ mOnShareTapped = () -> shareContent(clipData);
+ mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
} else if (clipData.getItemAt(0).getUri() != null) {
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- showShareChip(clipData);
+ mOnShareTapped = () -> shareContent(clipData);
+ mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
- } else {
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
}
} else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ mView.showDefaultTextPreview();
}
+ maybeShowRemoteCopy(clipData);
+ animateIn();
+ mView.announceForAccessibility(accessibilityAnnouncement);
+ mTimeoutHandler.resetTimeout();
+ }
+
+ private void maybeShowRemoteCopy(ClipData clipData) {
Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
// Only show remote copy if it's available.
PackageManager packageManager = mContext.getPackageManager();
if (packageManager.resolveActivity(
remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
- mRemoteCopyChip.setContentDescription(
- mContext.getString(R.string.clipboard_send_nearby_description));
- mRemoteCopyChip.setVisibility(View.VISIBLE);
- mRemoteCopyChip.setOnClickListener((v) -> {
+ mView.setRemoteCopyVisibility(true);
+ mOnRemoteCopyTapped = () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
mContext.startActivity(remoteCopyIntent);
animateOut();
- });
- mActionContainerBackground.setVisibility(View.VISIBLE);
+ };
} else {
- mRemoteCopyChip.setVisibility(View.GONE);
+ mView.setRemoteCopyVisibility(false);
}
- withWindowAttached(() -> {
- if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
- mView.post(this::animateIn);
- }
- mView.announceForAccessibility(accessibilityAnnouncement);
- });
- mTimeoutHandler.resetTimeout();
}
- void setOnSessionCompleteListener(Runnable runnable) {
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setOnSessionCompleteListener(Runnable runnable) {
mOnSessionCompleteListener = runnable;
}
@@ -418,72 +320,29 @@ public class ClipboardOverlayController {
actions.addAll(classification.getActions());
}
mView.post(() -> {
- resetActionChips();
- if (actions.size() > 0) {
- mActionContainerBackground.setVisibility(View.VISIBLE);
- for (RemoteAction action : actions) {
- Intent targetIntent = action.getActionIntent().getIntent();
- ComponentName component = targetIntent.getComponent();
- if (component != null && !TextUtils.equals(source,
- component.getPackageName())) {
- OverlayActionChip chip = constructActionChip(action);
- mActionContainer.addView(chip);
- mActionChips.add(chip);
- break; // only show at most one action chip
- }
- }
- }
- });
- }
-
- private void showShareChip(ClipData clip) {
- mShareChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mShareChip.setOnClickListener((v) -> shareContent(clip));
- }
-
- private OverlayActionChip constructActionChip(RemoteAction action) {
- OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.overlay_action_chip, mActionContainer, false);
- chip.setText(action.getTitle());
- chip.setContentDescription(action.getTitle());
- chip.setIcon(action.getIcon(), false);
- chip.setPendingIntent(action.getActionIntent(), () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
+ Optional<RemoteAction> action = actions.stream().filter(remoteAction -> {
+ ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+ return component != null && !TextUtils.equals(source, component.getPackageName());
+ }).findFirst();
+ mView.resetActionChips();
+ action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ }));
});
- chip.setAlpha(1);
- return chip;
}
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
- mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
- Looper.getMainLooper()) {
+ mInputEventReceiver = new InputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
- Region touchRegion = new Region();
-
- final Rect tmpRect = new Rect();
- mPreviewBorder.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mActionContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mDismissButton.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
- if (!touchRegion.contains(
+ if (!mView.isInTouchRegion(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
animateOut();
@@ -513,95 +372,27 @@ public class ClipboardOverlayController {
animateOut();
}
- private void showSinglePreview(View v) {
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.GONE);
- mHiddenPreview.setVisibility(View.GONE);
- v.setVisibility(View.VISIBLE);
- }
-
- private void showTextPreview(CharSequence text, TextView textView) {
- showSinglePreview(textView);
- final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
- textView.setText(truncatedText);
- updateTextSize(truncatedText, textView);
-
- textView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if (right - left != oldRight - oldLeft) {
- updateTextSize(truncatedText, textView);
- }
- });
- mEditChip.setVisibility(View.GONE);
- }
-
- private void updateTextSize(CharSequence text, TextView textView) {
- Paint paint = new Paint(textView.getPaint());
- Resources res = textView.getResources();
- float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
- float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
- if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
- // If the text is a single word and would fit within the TextView at the min font size,
- // find the biggest font size that will fit.
- float fontSizePx = minFontSize;
- while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
- && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
- fontSizePx += FONT_SEARCH_STEP_PX;
- }
- // Need to turn off autosizing, otherwise setTextSize is a no-op.
- textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
- // It's possible to hit the max font size and not fill the width, so centering
- // horizontally looks better in this case.
- textView.setGravity(Gravity.CENTER);
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
- } else {
- // Otherwise just stick with autosize.
- textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
- (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
- textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
- }
- }
-
- private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
- float fontSizePx) {
- paint.setTextSize(fontSizePx);
- float size = paint.measureText(text.toString());
- float availableWidth = textView.getWidth() - textView.getPaddingLeft()
- - textView.getPaddingRight();
- return size < availableWidth;
- }
-
- private static boolean isOneWord(CharSequence text) {
- return text.toString().split("\\s+", 2).length == 1;
- }
-
private void showEditableText(CharSequence text, boolean hidden) {
- TextView textView = hidden ? mHiddenPreview : mTextPreview;
- showTextPreview(text, textView);
- View.OnClickListener listener = v -> editText();
- setAccessibilityActionToEdit(textView);
+ mView.showTextPreview(text, hidden);
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_text_description));
- mEditChip.setOnClickListener(listener);
+ mOnEditTapped = this::editText;
+ mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description));
}
- textView.setOnClickListener(listener);
}
private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
- View.OnClickListener listener = v -> editImage(uri);
+ Runnable listener = () -> editImage(uri);
ContentResolver resolver = mContext.getContentResolver();
String mimeType = resolver.getType(uri);
boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
if (isSensitive) {
- mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
- showSinglePreview(mHiddenPreview);
+ mView.showImagePreview(null);
if (isEditableImage) {
- mHiddenPreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mHiddenPreview);
+ mOnPreviewTapped = listener;
+ mView.setEditAccessibilityAction(true);
}
} else if (isEditableImage) { // if the MIMEtype is image, try to load
try {
@@ -609,44 +400,36 @@ public class ClipboardOverlayController {
// The width of the view is capped, height maintains aspect ratio, so allow it to be
// taller if needed.
Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- showSinglePreview(mImagePreview);
- mImagePreview.setImageBitmap(thumbnail);
- mImagePreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mImagePreview);
+ mView.showImagePreview(thumbnail);
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = listener;
} catch (IOException e) {
Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
+ mView.showDefaultTextPreview();
isEditableImage = false;
}
} else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
+ mView.showDefaultTextPreview();
}
if (isEditableImage && DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setOnClickListener(listener);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_image_description));
+ mView.showEditChip(mContext.getString(R.string.clipboard_edit_image_description));
}
return isEditableImage;
}
- private void setAccessibilityActionToEdit(View view) {
- ViewCompat.replaceAccessibilityAction(view,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- mContext.getString(R.string.clipboard_edit), null);
- }
-
private void animateIn() {
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setVisibility(View.VISIBLE);
+ if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+ return;
}
- mEnterAnimator = getEnterAnimation();
+ mEnterAnimator = mView.getEnterAnimation();
+ mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mTimeoutHandler.resetTimeout();
+ }
+ });
mEnterAnimator.start();
}
@@ -654,7 +437,7 @@ public class ClipboardOverlayController {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
return;
}
- Animator anim = getExitAnimation();
+ Animator anim = mView.getExitAnimation();
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -676,122 +459,11 @@ public class ClipboardOverlayController {
anim.start();
}
- private Animator getEnterAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
- AnimatorSet enterAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(66);
- rootAnim.addUpdateListener(animation -> {
- mView.setAlpha(animation.getAnimatedFraction());
- });
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(333);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
- float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionsScaleX);
- mActionContainer.setScaleY(actionsScaleY);
- mActionContainerBackground.setScaleX(actionsScaleX);
- mActionContainerBackground.setScaleY(actionsScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(283);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- mActionContainer.setAlpha(0);
- mPreviewBorder.setAlpha(0);
- mClipboardPreview.setAlpha(0);
- enterAnim.play(rootAnim).with(scaleAnim);
- enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
- enterAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mView.setAlpha(1);
- mTimeoutHandler.resetTimeout();
- }
- });
- return enterAnim;
- }
-
- private Animator getExitAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
- AnimatorSet exitAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(100);
- rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(250);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
- float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionScaleX);
- mActionContainer.setScaleY(actionScaleY);
- mActionContainerBackground.setScaleX(actionScaleX);
- mActionContainerBackground.setScaleY(actionScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(166);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = 1 - animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- exitAnim.play(alphaAnim).with(scaleAnim);
- exitAnim.play(rootAnim).after(150).after(alphaAnim);
- return exitAnim;
- }
-
private void hideImmediate() {
// Note this may be called multiple times if multiple dismissal events happen at the same
// time.
mTimeoutHandler.cancelTimeout();
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(decorView);
- }
+ mWindow.remove();
if (mCloseDialogsReceiver != null) {
mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
mCloseDialogsReceiver = null;
@@ -813,129 +485,20 @@ public class ClipboardOverlayController {
}
}
- private void resetActionChips() {
- for (OverlayActionChip chip : mActionChips) {
- mActionContainer.removeView(chip);
- }
- mActionChips.clear();
- }
-
private void reset() {
- mView.setTranslationX(0);
- mView.setAlpha(0);
- mActionContainerBackground.setVisibility(View.GONE);
- mShareChip.setVisibility(View.GONE);
- mEditChip.setVisibility(View.GONE);
- mRemoteCopyChip.setVisibility(View.GONE);
- resetActionChips();
+ mOnRemoteCopyTapped = null;
+ mOnShareTapped = null;
+ mOnEditTapped = null;
+ mOnPreviewTapped = null;
+ mView.reset();
mTimeoutHandler.cancelTimeout();
mClipboardLogger.reset();
}
- @MainThread
- private void attachWindow() {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mBlockAttach) {
- return;
- }
- mBlockAttach = true;
- mWindowManager.addView(decorView, mWindowLayoutParams);
- decorView.requestApplyInsets();
- mView.requestApplyInsets();
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- }
-
- @Override
- public void onWindowDetached() {
- }
- }
- );
- }
-
- private void withWindowAttached(Runnable action) {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
- action.run();
- } else {
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
- action.run();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
- }
- }
-
- private void updateInsets(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
- if (p == null) {
- return;
- }
- DisplayCutout cutout = insets.getDisplayCutout();
- Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
- Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
- if (cutout == null) {
- p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
- } else {
- Insets waterfall = cutout.getWaterfallInsets();
- if (orientation == ORIENTATION_PORTRAIT) {
- p.setMargins(
- waterfall.left,
- Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom))));
- } else {
- p.setMargins(
- waterfall.left,
- waterfall.top,
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(navBarInsets.bottom, waterfall.bottom)));
- }
- }
- mView.setLayoutParams(p);
- mView.requestLayout();
- }
-
private Display getDefaultDisplay() {
return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
}
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- int flags = mWindowLayoutParams.flags;
- if (focusable) {
- mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
- if (mWindowLayoutParams.flags == flags) {
- return;
- }
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
- }
- }
-
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private boolean mGuarded = false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
new file mode 100644
index 000000000000..3a040829ba0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.app.ICompatCameraControlCallback;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
+ private static final String TAG = "ClipboardOverlayCtrlr";
+ private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+ /** Constants for screenshot/copy deconflicting */
+ public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+ public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+ public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+ private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+
+ private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+ private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+ private static final int FONT_SEARCH_STEP_PX = 4;
+
+ private final Context mContext;
+ private final ClipboardLogger mClipboardLogger;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final DisplayManager mDisplayManager;
+ private final DisplayMetrics mDisplayMetrics;
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ private final PhoneWindow mWindow;
+ private final TimeoutHandler mTimeoutHandler;
+ private final AccessibilityManager mAccessibilityManager;
+ private final TextClassifier mTextClassifier;
+
+ private final DraggableConstraintLayout mView;
+ private final View mClipboardPreview;
+ private final ImageView mImagePreview;
+ private final TextView mTextPreview;
+ private final TextView mHiddenPreview;
+ private final View mPreviewBorder;
+ private final OverlayActionChip mEditChip;
+ private final OverlayActionChip mShareChip;
+ private final OverlayActionChip mRemoteCopyChip;
+ private final View mActionContainerBackground;
+ private final View mDismissButton;
+ private final LinearLayout mActionContainer;
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+ private Runnable mOnSessionCompleteListener;
+
+ private InputMonitor mInputMonitor;
+ private InputEventReceiver mInputEventReceiver;
+
+ private BroadcastReceiver mCloseDialogsReceiver;
+ private BroadcastReceiver mScreenshotReceiver;
+
+ private boolean mBlockAttach = false;
+ private Animator mExitAnimator;
+ private Animator mEnterAnimator;
+ private final int mOrientation;
+ private boolean mKeyboardVisible;
+
+
+ public ClipboardOverlayControllerLegacy(Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ BroadcastSender broadcastSender,
+ TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+ final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+ mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+ mClipboardLogger = new ClipboardLogger(uiEventLogger);
+
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+ .getTextClassifier();
+
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+
+ mTimeoutHandler = timeoutHandler;
+ mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ClipboardOverlay");
+
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ setWindowFocusable(false);
+
+ mView = (DraggableConstraintLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
+ mActionContainerBackground =
+ requireNonNull(mView.findViewById(R.id.actions_container_background));
+ mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+ mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
+ mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+ mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
+ mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+ mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+ mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
+ mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+ mEditChip.setAlpha(1);
+ mShareChip.setAlpha(1);
+ mRemoteCopyChip.setAlpha(1);
+ mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
+
+ mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+ mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
+ @Override
+ public void onInteraction() {
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override
+ public void onSwipeDismissInitiated(Animator animator) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ mExitAnimator = animator;
+ }
+
+ @Override
+ public void onDismissComplete() {
+ hideImmediate();
+ }
+ });
+
+ mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+ int availableHeight = mTextPreview.getHeight()
+ - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+ mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ return true;
+ });
+
+ mDismissButton.setOnClickListener(view -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ animateOut();
+ });
+
+ mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mRemoteCopyChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+ mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+ mOrientation = mContext.getResources().getConfiguration().orientation;
+
+ attachWindow();
+ withWindowAttached(() -> {
+ mWindow.setContentView(mView);
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ updateInsets(insets);
+ mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ WindowInsets insets =
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ updateInsets(insets);
+ }
+ }
+ });
+ mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (mContext.getResources().getConfiguration().orientation
+ != mOrientation) {
+ mClipboardLogger.logSessionComplete(
+ CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ hideImmediate();
+ }
+ }
+
+ @Override
+ public void requestCompatCameraControl(
+ boolean showControl, boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "unexpected requestCompatCameraControl call");
+ }
+ });
+ });
+
+ mTimeoutHandler.setOnTimeoutRunnable(() -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
+ animateOut();
+ });
+
+ mCloseDialogsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ animateOut();
+ }
+ }
+ };
+
+ mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
+ new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+ mScreenshotReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ animateOut();
+ }
+ }
+ };
+
+ mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
+ new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
+ SELF_PERMISSION);
+ monitorOutsideTouches();
+
+ Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
+ // Set package name so the system knows it's safe
+ copyIntent.setPackage(mContext.getPackageName());
+ broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipData(ClipData clipData, String clipSource) {
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ mExitAnimator.cancel();
+ }
+ reset();
+ String accessibilityAnnouncement;
+
+ boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+ && clipData.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
+ if (clipData == null || clipData.getItemCount() == 0) {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+ ClipData.Item item = clipData.getItemAt(0);
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (item.getTextLinks() != null) {
+ AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
+ }
+ }
+ if (isSensitive) {
+ showEditableText(
+ mContext.getResources().getString(R.string.clipboard_asterisks), true);
+ } else {
+ showEditableText(item.getText(), false);
+ }
+ showShareChip(clipData);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
+ } else if (clipData.getItemAt(0).getUri() != null) {
+ if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
+ showShareChip(clipData);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
+ } else {
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ }
+ } else {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ }
+ Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
+ // Only show remote copy if it's available.
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager.resolveActivity(
+ remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
+ mRemoteCopyChip.setContentDescription(
+ mContext.getString(R.string.clipboard_send_nearby_description));
+ mRemoteCopyChip.setVisibility(View.VISIBLE);
+ mRemoteCopyChip.setOnClickListener((v) -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+ mContext.startActivity(remoteCopyIntent);
+ animateOut();
+ });
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ } else {
+ mRemoteCopyChip.setVisibility(View.GONE);
+ }
+ withWindowAttached(() -> {
+ if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
+ mView.post(this::animateIn);
+ }
+ mView.announceForAccessibility(accessibilityAnnouncement);
+ });
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setOnSessionCompleteListener(Runnable runnable) {
+ mOnSessionCompleteListener = runnable;
+ }
+
+ private void classifyText(ClipData.Item item, String source) {
+ ArrayList<RemoteAction> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+ TextClassification classification = mTextClassifier.classifyText(
+ item.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
+ mView.post(() -> {
+ resetActionChips();
+ if (actions.size() > 0) {
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ for (RemoteAction action : actions) {
+ Intent targetIntent = action.getActionIntent().getIntent();
+ ComponentName component = targetIntent.getComponent();
+ if (component != null && !TextUtils.equals(source,
+ component.getPackageName())) {
+ OverlayActionChip chip = constructActionChip(action);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ break; // only show at most one action chip
+ }
+ }
+ }
+ });
+ }
+
+ private void showShareChip(ClipData clip) {
+ mShareChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mShareChip.setOnClickListener((v) -> shareContent(clip));
+ }
+
+ private OverlayActionChip constructActionChip(RemoteAction action) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_action_chip, mActionContainer, false);
+ chip.setText(action.getTitle());
+ chip.setContentDescription(action.getTitle());
+ chip.setIcon(action.getIcon(), false);
+ chip.setPendingIntent(action.getActionIntent(), () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ chip.setAlpha(1);
+ return chip;
+ }
+
+ private void monitorOutsideTouches() {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+ mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
+ Looper.getMainLooper()) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ Region touchRegion = new Region();
+
+ final Rect tmpRect = new Rect();
+ mPreviewBorder.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+ -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ mActionContainerBackground.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+ -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ mDismissButton.getBoundsOnScreen(tmpRect);
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ if (!touchRegion.contains(
+ (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+ animateOut();
+ }
+ }
+ }
+ finishInputEvent(event, true /* handled */);
+ }
+ };
+ }
+
+ private void editImage(Uri uri) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
+ animateOut();
+ }
+
+ private void editText() {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
+ animateOut();
+ }
+
+ private void shareContent(ClipData clip) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
+ animateOut();
+ }
+
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.GONE);
+ mHiddenPreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private void showTextPreview(CharSequence text, TextView textView) {
+ showSinglePreview(textView);
+ final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
+ textView.setText(truncatedText);
+ updateTextSize(truncatedText, textView);
+
+ textView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (right - left != oldRight - oldLeft) {
+ updateTextSize(truncatedText, textView);
+ }
+ });
+ mEditChip.setVisibility(View.GONE);
+ }
+
+ private void updateTextSize(CharSequence text, TextView textView) {
+ Paint paint = new Paint(textView.getPaint());
+ Resources res = textView.getResources();
+ float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+ float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+ if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+ // If the text is a single word and would fit within the TextView at the min font size,
+ // find the biggest font size that will fit.
+ float fontSizePx = minFontSize;
+ while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+ && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+ fontSizePx += FONT_SEARCH_STEP_PX;
+ }
+ // Need to turn off autosizing, otherwise setTextSize is a no-op.
+ textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+ // It's possible to hit the max font size and not fill the width, so centering
+ // horizontally looks better in this case.
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+ } else {
+ // Otherwise just stick with autosize.
+ textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+ (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+ textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+ }
+ }
+
+ private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+ float fontSizePx) {
+ paint.setTextSize(fontSizePx);
+ float size = paint.measureText(text.toString());
+ float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+ - textView.getPaddingRight();
+ return size < availableWidth;
+ }
+
+ private static boolean isOneWord(CharSequence text) {
+ return text.toString().split("\\s+", 2).length == 1;
+ }
+
+ private void showEditableText(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenPreview : mTextPreview;
+ showTextPreview(text, textView);
+ View.OnClickListener listener = v -> editText();
+ setAccessibilityActionToEdit(textView);
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setContentDescription(
+ mContext.getString(R.string.clipboard_edit_text_description));
+ mEditChip.setOnClickListener(listener);
+ }
+ textView.setOnClickListener(listener);
+ }
+
+ private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
+ View.OnClickListener listener = v -> editImage(uri);
+ ContentResolver resolver = mContext.getContentResolver();
+ String mimeType = resolver.getType(uri);
+ boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
+ if (isSensitive) {
+ mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+ showSinglePreview(mHiddenPreview);
+ if (isEditableImage) {
+ mHiddenPreview.setOnClickListener(listener);
+ setAccessibilityActionToEdit(mHiddenPreview);
+ }
+ } else if (isEditableImage) { // if the MIMEtype is image, try to load
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ showSinglePreview(mImagePreview);
+ mImagePreview.setImageBitmap(thumbnail);
+ mImagePreview.setOnClickListener(listener);
+ setAccessibilityActionToEdit(mImagePreview);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ isEditableImage = false;
+ }
+ } else {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ }
+ if (isEditableImage && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setOnClickListener(listener);
+ mEditChip.setContentDescription(
+ mContext.getString(R.string.clipboard_edit_image_description));
+ }
+ return isEditableImage;
+ }
+
+ private void setAccessibilityActionToEdit(View view) {
+ ViewCompat.replaceAccessibilityAction(view,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ mContext.getString(R.string.clipboard_edit), null);
+ }
+
+ private void animateIn() {
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ mEnterAnimator = getEnterAnimation();
+ mEnterAnimator.start();
+ }
+
+ private void animateOut() {
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ return;
+ }
+ Animator anim = getExitAnimation();
+ anim.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!mCancelled) {
+ hideImmediate();
+ }
+ }
+ });
+ mExitAnimator = anim;
+ anim.start();
+ }
+
+ private Animator getEnterAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+ AnimatorSet enterAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(66);
+ rootAnim.addUpdateListener(animation -> {
+ mView.setAlpha(animation.getAnimatedFraction());
+ });
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(333);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+ float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionsScaleX);
+ mActionContainer.setScaleY(actionsScaleY);
+ mActionContainerBackground.setScaleX(actionsScaleX);
+ mActionContainerBackground.setScaleY(actionsScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(283);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ mActionContainer.setAlpha(0);
+ mPreviewBorder.setAlpha(0);
+ mClipboardPreview.setAlpha(0);
+ enterAnim.play(rootAnim).with(scaleAnim);
+ enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+ enterAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mView.setAlpha(1);
+ mTimeoutHandler.resetTimeout();
+ }
+ });
+ return enterAnim;
+ }
+
+ private Animator getExitAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+ AnimatorSet exitAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(100);
+ rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(250);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+ float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionScaleX);
+ mActionContainer.setScaleY(actionScaleY);
+ mActionContainerBackground.setScaleX(actionScaleX);
+ mActionContainerBackground.setScaleY(actionScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(166);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = 1 - animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ exitAnim.play(alphaAnim).with(scaleAnim);
+ exitAnim.play(rootAnim).after(150).after(alphaAnim);
+ return exitAnim;
+ }
+
+ private void hideImmediate() {
+ // Note this may be called multiple times if multiple dismissal events happen at the same
+ // time.
+ mTimeoutHandler.cancelTimeout();
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+ if (mCloseDialogsReceiver != null) {
+ mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
+ mCloseDialogsReceiver = null;
+ }
+ if (mScreenshotReceiver != null) {
+ mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
+ mScreenshotReceiver = null;
+ }
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ if (mOnSessionCompleteListener != null) {
+ mOnSessionCompleteListener.run();
+ }
+ }
+
+ private void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ }
+
+ private void reset() {
+ mView.setTranslationX(0);
+ mView.setAlpha(0);
+ mActionContainerBackground.setVisibility(View.GONE);
+ mShareChip.setVisibility(View.GONE);
+ mEditChip.setVisibility(View.GONE);
+ mRemoteCopyChip.setVisibility(View.GONE);
+ resetActionChips();
+ mTimeoutHandler.cancelTimeout();
+ mClipboardLogger.reset();
+ }
+
+ @MainThread
+ private void attachWindow() {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
+ return;
+ }
+ mBlockAttach = true;
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+ mView.requestApplyInsets();
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ }
+ );
+ }
+
+ private void withWindowAttached(Runnable action) {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+ }
+ }
+
+ private void updateInsets(WindowInsets insets) {
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+ if (p == null) {
+ return;
+ }
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+ if (cutout == null) {
+ p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+ } else {
+ Insets waterfall = cutout.getWaterfallInsets();
+ if (orientation == ORIENTATION_PORTRAIT) {
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom))));
+ } else {
+ p.setMargins(
+ waterfall.left,
+ waterfall.top,
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
+ }
+ }
+ mView.setLayoutParams(p);
+ mView.requestLayout();
+ }
+
+ private Display getDefaultDisplay() {
+ return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ return;
+ }
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+
+ static class ClipboardLogger {
+ private final UiEventLogger mUiEventLogger;
+ private boolean mGuarded = false;
+
+ ClipboardLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
+
+ void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
+ if (!mGuarded) {
+ mGuarded = true;
+ mUiEventLogger.log(event);
+ }
+ }
+
+ void reset() {
+ mGuarded = false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
index 8b0b2a59dd92..0d989a78947d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
@@ -27,17 +27,17 @@ import com.android.systemui.screenshot.TimeoutHandler;
import javax.inject.Inject;
/**
- * A factory that churns out ClipboardOverlayControllers on demand.
+ * A factory that churns out ClipboardOverlayControllerLegacys on demand.
*/
@SysUISingleton
-public class ClipboardOverlayControllerFactory {
+public class ClipboardOverlayControllerLegacyFactory {
private final UiEventLogger mUiEventLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BroadcastSender mBroadcastSender;
@Inject
- public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
+ public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
this.mBroadcastDispatcher = broadcastDispatcher;
this.mBroadcastSender = broadcastSender;
@@ -45,10 +45,10 @@ public class ClipboardOverlayControllerFactory {
}
/**
- * One new ClipboardOverlayController, coming right up!
+ * One new ClipboardOverlayControllerLegacy, coming right up!
*/
- public ClipboardOverlayController create(Context context) {
- return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
+ public ClipboardOverlayControllerLegacy create(Context context) {
+ return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
new TimeoutHandler(context), mUiEventLogger);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
new file mode 100644
index 000000000000..2d3315759371
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -0,0 +1,482 @@
+/*
+ * 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.clipboardoverlay;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the visual elements and animations for the clipboard overlay.
+ */
+public class ClipboardOverlayView extends DraggableConstraintLayout {
+
+ interface ClipboardOverlayCallbacks extends SwipeDismissCallbacks {
+ void onDismissButtonTapped();
+
+ void onRemoteCopyButtonTapped();
+
+ void onEditButtonTapped();
+
+ void onShareButtonTapped();
+
+ void onPreviewTapped();
+ }
+
+ private static final String TAG = "ClipboardView";
+
+ private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+ private static final int FONT_SEARCH_STEP_PX = 4;
+
+ private final DisplayMetrics mDisplayMetrics;
+ private final AccessibilityManager mAccessibilityManager;
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+ private View mClipboardPreview;
+ private ImageView mImagePreview;
+ private TextView mTextPreview;
+ private TextView mHiddenPreview;
+ private View mPreviewBorder;
+ private OverlayActionChip mEditChip;
+ private OverlayActionChip mShareChip;
+ private OverlayActionChip mRemoteCopyChip;
+ private View mActionContainerBackground;
+ private View mDismissButton;
+ private LinearLayout mActionContainer;
+
+ public ClipboardOverlayView(Context context) {
+ this(context, null);
+ }
+
+ public ClipboardOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClipboardOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mActionContainerBackground =
+ requireNonNull(findViewById(R.id.actions_container_background));
+ mActionContainer = requireNonNull(findViewById(R.id.actions));
+ mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
+ mImagePreview = requireNonNull(findViewById(R.id.image_preview));
+ mTextPreview = requireNonNull(findViewById(R.id.text_preview));
+ mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
+ mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
+ mEditChip = requireNonNull(findViewById(R.id.edit_chip));
+ mShareChip = requireNonNull(findViewById(R.id.share_chip));
+ mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
+ mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+
+ mEditChip.setAlpha(1);
+ mShareChip.setAlpha(1);
+ mRemoteCopyChip.setAlpha(1);
+ mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+
+ mEditChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mRemoteCopyChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+ mShareChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+
+ mRemoteCopyChip.setContentDescription(
+ mContext.getString(R.string.clipboard_send_nearby_description));
+
+ mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+ int availableHeight = mTextPreview.getHeight()
+ - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+ mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ return true;
+ });
+ super.onFinishInflate();
+ }
+
+ @Override
+ public void setCallbacks(SwipeDismissCallbacks callbacks) {
+ super.setCallbacks(callbacks);
+ ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
+ mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped());
+ mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+ mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
+ mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
+ mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+ }
+
+ void setEditAccessibilityAction(boolean editable) {
+ if (editable) {
+ ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ mContext.getString(R.string.clipboard_edit), null);
+ } else {
+ ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ null, null);
+ }
+ }
+
+ void setInsets(WindowInsets insets, int orientation) {
+ FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
+ if (p == null) {
+ return;
+ }
+ Rect margins = computeMargins(insets, orientation);
+ p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
+ setLayoutParams(p);
+ requestLayout();
+ }
+
+ boolean isInTouchRegion(int x, int y) {
+ Region touchRegion = new Region();
+ final Rect tmpRect = new Rect();
+
+ mPreviewBorder.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ mActionContainerBackground.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ mDismissButton.getBoundsOnScreen(tmpRect);
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ return touchRegion.contains(x, y);
+ }
+
+ void setRemoteCopyVisibility(boolean visible) {
+ if (visible) {
+ mRemoteCopyChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ } else {
+ mRemoteCopyChip.setVisibility(View.GONE);
+ }
+ }
+
+ void showDefaultTextPreview() {
+ String copied = mContext.getString(R.string.clipboard_overlay_text_copied);
+ showTextPreview(copied, false);
+ }
+
+ void showTextPreview(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenPreview : mTextPreview;
+ showSinglePreview(textView);
+ textView.setText(text.subSequence(0, Math.min(500, text.length())));
+ updateTextSize(text, textView);
+ textView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (right - left != oldRight - oldLeft) {
+ updateTextSize(text, textView);
+ }
+ });
+ mEditChip.setVisibility(View.GONE);
+ }
+
+ void showImagePreview(@Nullable Bitmap thumbnail) {
+ if (thumbnail == null) {
+ mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+ showSinglePreview(mHiddenPreview);
+ } else {
+ mImagePreview.setImageBitmap(thumbnail);
+ showSinglePreview(mImagePreview);
+ }
+ }
+
+ void showEditChip(String contentDescription) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setContentDescription(contentDescription);
+ }
+
+ void showShareChip() {
+ mShareChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ }
+
+ void reset() {
+ setTranslationX(0);
+ setAlpha(0);
+ mActionContainerBackground.setVisibility(View.GONE);
+ mDismissButton.setVisibility(View.GONE);
+ mShareChip.setVisibility(View.GONE);
+ mEditChip.setVisibility(View.GONE);
+ mRemoteCopyChip.setVisibility(View.GONE);
+ setEditAccessibilityAction(false);
+ resetActionChips();
+ }
+
+ void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ }
+
+ Animator getEnterAnimation() {
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+ AnimatorSet enterAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(66);
+ rootAnim.addUpdateListener(animation -> {
+ setAlpha(animation.getAnimatedFraction());
+ });
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(333);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+ float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionsScaleX);
+ mActionContainer.setScaleY(actionsScaleY);
+ mActionContainerBackground.setScaleX(actionsScaleX);
+ mActionContainerBackground.setScaleY(actionsScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(283);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ mActionContainer.setAlpha(0);
+ mPreviewBorder.setAlpha(0);
+ mClipboardPreview.setAlpha(0);
+ enterAnim.play(rootAnim).with(scaleAnim);
+ enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+ enterAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ setAlpha(1);
+ }
+ });
+ return enterAnim;
+ }
+
+ Animator getExitAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+ AnimatorSet exitAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(100);
+ rootAnim.addUpdateListener(anim -> setAlpha(1 - anim.getAnimatedFraction()));
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(250);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+ float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionScaleX);
+ mActionContainer.setScaleY(actionScaleY);
+ mActionContainerBackground.setScaleX(actionScaleX);
+ mActionContainerBackground.setScaleY(actionScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(166);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = 1 - animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ exitAnim.play(alphaAnim).with(scaleAnim);
+ exitAnim.play(rootAnim).after(150).after(alphaAnim);
+ return exitAnim;
+ }
+
+ void setActionChip(RemoteAction action, Runnable onFinish) {
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ OverlayActionChip chip = constructActionChip(action, onFinish);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ }
+
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.GONE);
+ mHiddenPreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_action_chip, mActionContainer, false);
+ chip.setText(action.getTitle());
+ chip.setContentDescription(action.getTitle());
+ chip.setIcon(action.getIcon(), false);
+ chip.setPendingIntent(action.getActionIntent(), onFinish);
+ chip.setAlpha(1);
+ return chip;
+ }
+
+ private static void updateTextSize(CharSequence text, TextView textView) {
+ Paint paint = new Paint(textView.getPaint());
+ Resources res = textView.getResources();
+ float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+ float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+ if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+ // If the text is a single word and would fit within the TextView at the min font size,
+ // find the biggest font size that will fit.
+ float fontSizePx = minFontSize;
+ while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+ && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+ fontSizePx += FONT_SEARCH_STEP_PX;
+ }
+ // Need to turn off autosizing, otherwise setTextSize is a no-op.
+ textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+ // It's possible to hit the max font size and not fill the width, so centering
+ // horizontally looks better in this case.
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+ } else {
+ // Otherwise just stick with autosize.
+ textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+ (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+ textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+ }
+ }
+
+ private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+ float fontSizePx) {
+ paint.setTextSize(fontSizePx);
+ float size = paint.measureText(text.toString());
+ float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+ - textView.getPaddingRight();
+ return size < availableWidth;
+ }
+
+ private static boolean isOneWord(CharSequence text) {
+ return text.toString().split("\\s+", 2).length == 1;
+ }
+
+ private static Rect computeMargins(WindowInsets insets, int orientation) {
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+ if (cutout == null) {
+ return new Rect(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+ } else {
+ Insets waterfall = cutout.getWaterfallInsets();
+ if (orientation == ORIENTATION_PORTRAIT) {
+ return new Rect(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom))));
+ } else {
+ return new Rect(
+ waterfall.left,
+ waterfall.top,
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
new file mode 100644
index 000000000000..9dac9b393d60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -0,0 +1,174 @@
+/*
+ * 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.clipboardoverlay;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ICompatCameraControlCallback;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles attaching the window and the window insets for the clipboard overlay.
+ */
+public class ClipboardOverlayWindow extends PhoneWindow
+ implements ViewRootImpl.ActivityConfigCallback {
+ private static final String TAG = "ClipboardOverlayWindow";
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+
+ private boolean mKeyboardVisible;
+ private final int mOrientation;
+ private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener;
+ private Runnable mOnOrientationChangeListener;
+
+ @Inject
+ ClipboardOverlayWindow(@OverlayWindowContext Context context) {
+ super(context);
+ mContext = context;
+ mOrientation = mContext.getResources().getConfiguration().orientation;
+
+ // Setup the window that we are going to use
+ requestFeature(Window.FEATURE_NO_TITLE);
+ requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+ setBackgroundDrawableResource(android.R.color.transparent);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ClipboardOverlay");
+ setWindowManager(mWindowManager, null, null);
+ setWindowFocusable(false);
+ }
+
+ /**
+ * Set callbacks for keyboard state change and orientation change and attach the window
+ *
+ * @param onKeyboardChangeListener callback for IME visibility changes
+ * @param onOrientationChangeListener callback for device orientation changes
+ */
+ public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener,
+ @NonNull Runnable onOrientationChangeListener) {
+ mOnKeyboardChangeListener = onKeyboardChangeListener;
+ mOnOrientationChangeListener = onOrientationChangeListener;
+
+ attach();
+ withWindowAttached(() -> {
+ WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
+ peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ mOnKeyboardChangeListener.accept(insets, mOrientation);
+ }
+ });
+ peekDecorView().getViewRootImpl().setActivityConfigCallback(this);
+ });
+ }
+
+ @Override // ViewRootImpl.ActivityConfigCallback
+ public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) {
+ if (mContext.getResources().getConfiguration().orientation != mOrientation) {
+ mOnOrientationChangeListener.run();
+ }
+ }
+
+ @Override // ViewRootImpl.ActivityConfigCallback
+ public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "unexpected requestCompatCameraControl call");
+ }
+
+ void remove() {
+ final View decorView = peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+ }
+
+ WindowInsets getWindowInsets() {
+ return mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ }
+
+ void withWindowAttached(Runnable action) {
+ View decorView = getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+ }
+ }
+
+ @MainThread
+ private void attach() {
+ View decorView = getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ return;
+ }
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ return;
+ }
+ final View decorView = peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
new file mode 100644
index 000000000000..22448130f7e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -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.systemui.clipboardoverlay.dagger;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.LayoutInflater;
+
+import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for {@link com.android.systemui.clipboardoverlay}. */
+@Module
+public interface ClipboardOverlayModule {
+
+ /**
+ *
+ */
+ @Provides
+ @OverlayWindowContext
+ static Context provideWindowContext(DisplayManager displayManager, Context context) {
+ Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ return context.createWindowContext(display, TYPE_SCREENSHOT, null);
+ }
+
+ /**
+ *
+ */
+ @Provides
+ static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
+ return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+ R.layout.clipboard_overlay, null);
+ }
+
+ @Qualifier
+ @Documented
+ @Retention(RUNTIME)
+ @interface OverlayWindowContext {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index c2dffe8bdad0..d05bd5120872 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -63,6 +64,7 @@ import dagger.Subcomponent;
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index d70b971dba14..d7638d663dc9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,6 +33,7 @@ import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.dagger.DemoModeModule;
@@ -61,7 +62,6 @@ import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -119,6 +119,7 @@ import dagger.Provides;
AssistModule.class,
BiometricsModule.class,
BouncerViewModule.class,
+ ClipboardOverlayModule.class,
ClockModule.class,
CoroutinesModule.class,
DreamModule.class,
@@ -133,7 +134,6 @@ import dagger.Provides;
PeopleModule.class,
PluginModule.class,
PrivacyModule.class,
- QsFrameTranslateModule.class,
ScreenshotModule.class,
SensorModule.class,
MultiUserUtilsModule.class,
@@ -167,12 +167,16 @@ public abstract class SystemUIModule {
@Binds
abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache);
- /** */
+ /**
+ *
+ */
@Binds
public abstract ContextComponentHelper bindComponentHelper(
ContextComponentResolver componentHelper);
- /** */
+ /**
+ *
+ */
@Binds
public abstract NotificationRowBinder bindNotificationRowBinder(
NotificationRowBinderImpl notificationRowBinder);
@@ -211,6 +215,7 @@ public abstract class SystemUIModule {
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
// TODO: This should provided by the WM component
+
/** Provides Optional of BubbleManager */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index 99ca3c76cf8d..d145f5c14917 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -40,11 +40,12 @@ import javax.inject.Inject;
* {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
* the designated dream overlay component.
*/
-public class DreamOverlayRegistrant extends CoreStartable {
+public class DreamOverlayRegistrant implements CoreStartable {
private static final String TAG = "DreamOverlayRegistrant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final IDreamManager mDreamManager;
private final ComponentName mOverlayServiceComponent;
+ private final Context mContext;
private final Resources mResources;
private boolean mCurrentRegisteredState = false;
@@ -98,7 +99,7 @@ public class DreamOverlayRegistrant extends CoreStartable {
@Inject
public DreamOverlayRegistrant(Context context, @Main Resources resources) {
- super(context);
+ mContext = context;
mResources = resources;
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.getService(DreamService.DREAM_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index bbcab60d7ba2..ee2f1af6a99b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.complication;
-import android.content.Context;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -37,7 +36,7 @@ import javax.inject.Inject;
* user, and pushes updates to {@link DreamOverlayStateController}.
*/
@SysUISingleton
-public class ComplicationTypesUpdater extends CoreStartable {
+public class ComplicationTypesUpdater implements CoreStartable {
private final DreamBackend mDreamBackend;
private final Executor mExecutor;
private final SecureSettings mSecureSettings;
@@ -45,13 +44,11 @@ public class ComplicationTypesUpdater extends CoreStartable {
private final DreamOverlayStateController mDreamOverlayStateController;
@Inject
- ComplicationTypesUpdater(Context context,
+ ComplicationTypesUpdater(
DreamBackend dreamBackend,
@Main Executor executor,
SecureSettings secureSettings,
DreamOverlayStateController dreamOverlayStateController) {
- super(context);
-
mDreamBackend = dreamBackend;
mExecutor = executor;
mSecureSettings = secureSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 675a2f46d310..77e1fc91e6ee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -19,7 +19,6 @@ package com.android.systemui.dreams.complication;
import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
-import android.content.Context;
import android.view.View;
import com.android.systemui.CoreStartable;
@@ -61,7 +60,7 @@ public class DreamClockTimeComplication implements Complication {
* {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with
* SystemUI.
*/
- public static class Registrant extends CoreStartable {
+ public static class Registrant implements CoreStartable {
private final DreamOverlayStateController mDreamOverlayStateController;
private final DreamClockTimeComplication mComplication;
@@ -69,10 +68,9 @@ public class DreamClockTimeComplication implements Complication {
* Default constructor to register {@link DreamClockTimeComplication}.
*/
@Inject
- public Registrant(Context context,
+ public Registrant(
DreamOverlayStateController dreamOverlayStateController,
DreamClockTimeComplication dreamClockTimeComplication) {
- super(context);
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = dreamClockTimeComplication;
}
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 821e13ef733c..0ccb222c8acc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -71,7 +71,7 @@ public class DreamHomeControlsComplication implements Complication {
/**
* {@link CoreStartable} for registering the complication with SystemUI on startup.
*/
- public static class Registrant extends CoreStartable {
+ public static class Registrant implements CoreStartable {
private final DreamHomeControlsComplication mComplication;
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
@@ -90,11 +90,9 @@ public class DreamHomeControlsComplication implements Complication {
};
@Inject
- public Registrant(Context context, DreamHomeControlsComplication complication,
+ public Registrant(DreamHomeControlsComplication complication,
DreamOverlayStateController dreamOverlayStateController,
ControlsComponent controlsComponent) {
- super(context);
-
mComplication = complication;
mControlsComponent = controlsComponent;
mDreamOverlayStateController = dreamOverlayStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index a981f255a873..c3aaf0cbf2d7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -61,7 +61,7 @@ public class SmartSpaceComplication implements Complication {
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
*/
- public static class Registrant extends CoreStartable {
+ public static class Registrant implements CoreStartable {
private final DreamSmartspaceController mSmartSpaceController;
private final DreamOverlayStateController mDreamOverlayStateController;
private final SmartSpaceComplication mComplication;
@@ -78,11 +78,10 @@ public class SmartSpaceComplication implements Complication {
* Default constructor for {@link SmartSpaceComplication}.
*/
@Inject
- public Registrant(Context context,
+ public Registrant(
DreamOverlayStateController dreamOverlayStateController,
SmartSpaceComplication smartSpaceComplication,
DreamSmartspaceController smartSpaceController) {
- super(context);
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = smartSpaceComplication;
mSmartSpaceController = smartSpaceController;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index c0e30211e018..560dcbd78c42 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -16,9 +16,7 @@
package com.android.systemui.flags
-import android.content.Context
import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
import dagger.Binds
@@ -30,12 +28,11 @@ import javax.inject.Inject
class FeatureFlagsDebugStartable
@Inject
constructor(
- @Application context: Context,
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
featureFlags: FeatureFlags
-) : CoreStartable(context) {
+) : CoreStartable {
init {
dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index f138f1e8aa79..e7d8cc362c56 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -16,9 +16,7 @@
package com.android.systemui.flags
-import android.content.Context
import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import dagger.Binds
import dagger.Module
@@ -28,8 +26,7 @@ import javax.inject.Inject
class FeatureFlagsReleaseStartable
@Inject
-constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) :
- CoreStartable(context) {
+constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable {
init {
dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 3dbadb0f07c9..7f08f868e30b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -105,10 +105,6 @@ public class Flags {
*/
public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
- /** Whether UserSwitcherActivity should use modern architecture. */
- public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
- new ReleasedFlag(209, true);
-
/**
* Whether the user interactor and repository should use `UserSwitcherController`.
*
@@ -309,6 +305,9 @@ public class Flags {
public static final UnreleasedFlag A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
new UnreleasedFlag(1600);
+ // 1700 - clipboard
+ public static final UnreleasedFlag CLIPBOARD_OVERLAY_REFACTOR = new UnreleasedFlag(1700);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 74d5bd577cf4..9f321d83d292 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -36,8 +36,7 @@ import javax.inject.Provider;
* Manages power menu plugins and communicates power menu actions to the CentralSurfaces.
*/
@SysUISingleton
-public class GlobalActionsComponent extends CoreStartable
- implements Callbacks, GlobalActionsManager {
+public class GlobalActionsComponent implements CoreStartable, Callbacks, GlobalActionsManager {
private final CommandQueue mCommandQueue;
private final ExtensionController mExtensionController;
@@ -48,11 +47,10 @@ public class GlobalActionsComponent extends CoreStartable
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Inject
- public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+ public GlobalActionsComponent(CommandQueue commandQueue,
ExtensionController extensionController,
Provider<GlobalActions> globalActionsProvider,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- super(context);
mCommandQueue = commandQueue;
mExtensionController = extensionController;
mGlobalActionsProvider = globalActionsProvider;
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 568143c8df71..4f1a2b34f07c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -27,7 +27,6 @@ import android.bluetooth.le.ScanSettings;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.HandlerThread;
@@ -66,7 +65,7 @@ import javax.inject.Provider;
/** */
@SysUISingleton
-public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener {
+public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChangedListener {
private static final String TAG = "KeyboardUI";
private static final boolean DEBUG = false;
@@ -127,13 +126,12 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo
@Inject
public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) {
- super(context);
+ mContext = context;
this.mBluetoothManagerProvider = bluetoothManagerProvider;
}
@Override
public void start() {
- mContext = super.mContext;
HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mHandler = new KeyboardHandler(thread.getLooper());
@@ -141,10 +139,6 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- }
-
- @Override
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyboardUI:");
pw.println(" mEnabled=" + mEnabled);
@@ -156,7 +150,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo
}
@Override
- protected void onBootCompleted() {
+ public void onBootCompleted() {
mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index aee70eeca12b..99de9eb4166e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -186,7 +186,7 @@ import dagger.Lazy;
* directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI
* thread of the keyguard.
*/
-public class KeyguardViewMediator extends CoreStartable implements Dumpable,
+public class KeyguardViewMediator implements CoreStartable, Dumpable,
StatusBarStateController.StateListener {
private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
@@ -272,6 +272,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
+ private final Context mContext;
private final FalsingCollector mFalsingCollector;
/** High level access to the power manager for WakeLocks */
@@ -1128,7 +1129,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
DreamOverlayStateController dreamOverlayStateController,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
- super(context);
+ mContext = context;
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 01cd3e28472e..f663b0dd23cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
@@ -67,19 +67,19 @@ constructor(
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
- * @param animationController An optional controller for the activity-launch animation
+ * @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
fun onQuickAffordanceClicked(
configKey: KClass<out KeyguardQuickAffordanceConfig>,
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
) {
@Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
- when (val result = config.onQuickAffordanceClicked(animationController)) {
+ when (val result = config.onQuickAffordanceClicked(expandable)) {
is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
- animationController = animationController
+ expandable = expandable,
)
is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
}
@@ -104,6 +104,7 @@ constructor(
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
icon = visibleState.icon,
+ toggle = visibleState.toggle,
)
} else {
KeyguardQuickAffordanceModel.Hidden
@@ -114,7 +115,7 @@ constructor(
private fun launchQuickAffordance(
intent: Intent,
canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
) {
@LockPatternUtils.StrongAuthTracker.StrongAuthFlags
val strongAuthFlags =
@@ -130,13 +131,13 @@ constructor(
activityStarter.postStartActivityDismissingKeyguard(
intent,
0 /* delay */,
- animationController
+ expandable?.activityLaunchController(),
)
} else {
activityStarter.startActivity(
intent,
true /* dismissShade */,
- animationController,
+ expandable?.activityLaunchController(),
true /* showOverLockscreenWhenLocked */,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eb6bb92dd912..e56b25967910 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import kotlin.reflect.KClass
/**
@@ -35,5 +36,7 @@ sealed class KeyguardQuickAffordanceModel {
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
/** An icon for the affordance. */
val icon: Icon,
+ /** The toggle state for the affordance. */
+ val toggle: KeyguardQuickAffordanceToggleState,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 89604f054f9b..83842602cbee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
@@ -61,7 +61,7 @@ constructor(
}
override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnClickedResult {
return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
intent =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8e1c6b76079f..95027d00c46c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,8 +18,9 @@
package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -27,9 +28,7 @@ interface KeyguardQuickAffordanceConfig {
val state: Flow<State>
- fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?
- ): OnClickedResult
+ fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
/**
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
@@ -44,6 +43,9 @@ interface KeyguardQuickAffordanceConfig {
data class Visible(
/** An icon for the affordance. */
val icon: Icon,
+ /** The toggle state for the affordance. */
+ val toggle: KeyguardQuickAffordanceToggleState =
+ KeyguardQuickAffordanceToggleState.NotSupported,
) : State()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d97deaf3b69e..502a6070a422 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -18,7 +18,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
@@ -66,7 +66,7 @@ constructor(
}
override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnClickedResult {
return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
intent = controller.intent,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 9196b0920bb4..a24a0d62465f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,7 @@ import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
import android.util.Log
import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
@@ -84,11 +84,11 @@ constructor(
}
override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnClickedResult {
walletController.startQuickAccessUiIntent(
activityStarter,
- animationController,
+ expandable?.activityLaunchController(),
/* hasCard= */ true,
)
return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
new file mode 100644
index 000000000000..55d38a41849d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.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.keyguard.shared.quickaffordance
+
+/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
+sealed class KeyguardQuickAffordanceToggleState {
+ /** Toggling is not supported. */
+ object NotSupported : KeyguardQuickAffordanceToggleState()
+ /** The quick affordance is on. */
+ object On : KeyguardQuickAffordanceToggleState()
+ /** The quick affordance is off. */
+ object Off : KeyguardQuickAffordanceToggleState()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 65b85c0355db..2c99ca59ba6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -29,7 +29,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.Utils
import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Interpolators
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
@@ -238,14 +238,26 @@ object KeyguardBottomAreaViewBinder {
IconViewBinder.bind(viewModel.icon, view)
+ view.isActivated = viewModel.isActivated
view.drawable.setTint(
Utils.getColorAttrDefaultColor(
view.context,
- com.android.internal.R.attr.textColorPrimary
+ if (viewModel.isActivated) {
+ com.android.internal.R.attr.textColorPrimaryInverse
+ } else {
+ com.android.internal.R.attr.textColorPrimary
+ },
)
)
view.backgroundTintList =
- Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+ Utils.getColorAttr(
+ view.context,
+ if (viewModel.isActivated) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.colorSurface
+ }
+ )
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
@@ -268,7 +280,7 @@ object KeyguardBottomAreaViewBinder {
viewModel.onClicked(
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = viewModel.configKey,
- animationController = ActivityLaunchAnimator.Controller.fromView(view),
+ expandable = Expandable.fromView(view),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 970ee4c541d7..535ca7210244 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -119,10 +120,11 @@ constructor(
onClicked = { parameters ->
quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey,
- animationController = parameters.animationController,
+ expandable = parameters.expandable,
)
},
isClickable = isClickable,
+ isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 0971f13e58dd..bf598ba85932 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
@@ -30,9 +30,10 @@ data class KeyguardQuickAffordanceViewModel(
val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
val onClicked: (OnClickedParameters) -> Unit = {},
val isClickable: Boolean = false,
+ val isActivated: Boolean = false,
) {
data class OnClickedParameters(
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
- val animationController: ActivityLaunchAnimator.Controller?,
+ val expandable: Expandable?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index 8f9357abbba8..c7e4c5e93090 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -21,7 +21,6 @@ import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
@@ -48,7 +47,7 @@ import javax.inject.Inject;
* session. Can be used across processes via StatusBarManagerService#registerSessionListener
*/
@SysUISingleton
-public class SessionTracker extends CoreStartable {
+public class SessionTracker implements CoreStartable {
private static final String TAG = "SessionTracker";
private static final boolean DEBUG = false;
@@ -65,13 +64,11 @@ public class SessionTracker extends CoreStartable {
@Inject
public SessionTracker(
- Context context,
IStatusBarService statusBarService,
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController
) {
- super(context);
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 9dd18b21cc71..80bff83d03a0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -11,6 +11,7 @@ import android.util.MathUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.animation.PathInterpolator
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.InstanceId
@@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor(
* finished
*/
@MediaLocation
- private var currentEndLocation: Int = -1
+ @VisibleForTesting
+ var currentEndLocation: Int = -1
/**
* The ending location of the view where it ends when all animations and transitions have
@@ -126,7 +128,8 @@ class MediaCarouselController @Inject constructor(
lateinit var settingsButton: View
private set
private val mediaContent: ViewGroup
- private val pageIndicator: PageIndicator
+ @VisibleForTesting
+ val pageIndicator: PageIndicator
private val visualStabilityCallback: OnReorderingAllowedListener
private var needsReordering: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
@@ -149,6 +152,27 @@ class MediaCarouselController @Inject constructor(
}
}
}
+
+ companion object {
+ const val ANIMATION_BASE_DURATION = 2200f
+ const val DURATION = 167f
+ const val DETAILS_DELAY = 1067f
+ const val CONTROLS_DELAY = 1400f
+ const val PAGINATION_DELAY = 1900f
+ const val MEDIATITLES_DELAY = 1000f
+ const val MEDIACONTAINERS_DELAY = 967f
+ val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F)
+ val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F)
+
+ fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
+ val transformStartFraction = delay / ANIMATION_BASE_DURATION
+ val transformDurationFraction = duration / ANIMATION_BASE_DURATION
+ val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
+ return MathUtils.constrain((squishinessToTime - transformStartFraction) /
+ transformDurationFraction, 0F, 1F)
+ }
+ }
+
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may occur
@@ -633,12 +657,17 @@ class MediaCarouselController @Inject constructor(
}
}
- private fun updatePageIndicatorAlpha() {
+ @VisibleForTesting
+ fun updatePageIndicatorAlpha() {
val hostStates = mediaHostStatesManager.mediaHostStates
val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
val startAlpha = if (startIsVisible) 1.0f else 0.0f
- val endAlpha = if (endIsVisible) 1.0f else 0.0f
+ // when squishing in split shade, only use endState, which keeps changing
+ // to provide squishFraction
+ val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
+ val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
+ calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
var alpha = 1.0f
if (!endIsVisible || !startIsVisible) {
var progress = currentTransitionProgress
@@ -687,6 +716,7 @@ class MediaCarouselController @Inject constructor(
mediaCarouselScrollHandler.setCarouselBounds(
currentCarouselWidth, currentCarouselHeight)
updatePageIndicatorLocation()
+ updatePageIndicatorAlpha()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index bffb0fdec707..864592238b73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -203,6 +203,14 @@ class MediaHost constructor(
}
}
+ override var squishFraction: Float = 1.0f
+ set(value) {
+ if (!value.equals(field)) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
override var showsOnlyActiveMedia: Boolean = false
set(value) {
if (!value.equals(field)) {
@@ -253,6 +261,7 @@ class MediaHost constructor(
override fun copy(): MediaHostState {
val mediaHostState = MediaHostStateHolder()
mediaHostState.expansion = expansion
+ mediaHostState.squishFraction = squishFraction
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
mediaHostState.measurementInput = measurementInput?.copy()
mediaHostState.visible = visible
@@ -271,6 +280,9 @@ class MediaHost constructor(
if (expansion != other.expansion) {
return false
}
+ if (squishFraction != other.squishFraction) {
+ return false
+ }
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
return false
}
@@ -289,6 +301,7 @@ class MediaHost constructor(
override fun hashCode(): Int {
var result = measurementInput?.hashCode() ?: 0
result = 31 * result + expansion.hashCode()
+ result = 31 * result + squishFraction.hashCode()
result = 31 * result + falsingProtectionNeeded.hashCode()
result = 31 * result + showsOnlyActiveMedia.hashCode()
result = 31 * result + if (visible) 1 else 2
@@ -329,6 +342,11 @@ interface MediaHostState {
var expansion: Float
/**
+ * Fraction of the height animation.
+ */
+ var squishFraction: Float
+
+ /**
* Is this host only showing active media or is it showing all of them including resumption?
*/
var showsOnlyActiveMedia: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index ac59175d4646..faa7aaee3c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -18,8 +18,15 @@ package com.android.systemui.media
import android.content.Context
import android.content.res.Configuration
+import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
+import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
@@ -50,6 +57,24 @@ class MediaViewController @Inject constructor(
companion object {
@JvmField
val GUTS_ANIMATION_DURATION = 500L
+ val controlIds = setOf(
+ R.id.media_progress_bar,
+ R.id.actionNext,
+ R.id.actionPrev,
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4,
+ R.id.media_scrubbing_elapsed_time,
+ R.id.media_scrubbing_total_time
+ )
+
+ val detailIds = setOf(
+ R.id.header_title,
+ R.id.header_artist,
+ R.id.actionPlayPause,
+ )
}
/**
@@ -57,6 +82,7 @@ class MediaViewController @Inject constructor(
*/
lateinit var sizeChangedListener: () -> Unit
private var firstRefresh: Boolean = true
+ @VisibleForTesting
private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
private var animationDelay: Long = 0
@@ -279,10 +305,47 @@ class MediaViewController @Inject constructor(
}
/**
+ * Apply squishFraction to a copy of viewState such that the cached version is untouched.
+ */
+ internal fun squishViewState(
+ viewState: TransitionViewState,
+ squishFraction: Float
+ ): TransitionViewState {
+ val squishedViewState = viewState.copy()
+ squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
+ controlIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
+ }
+ }
+
+ detailIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
+ }
+ }
+
+ RecommendationViewHolder.mediaContainersIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
+ }
+ }
+
+ RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
+ squishedViewState.widgetStates.get(id)?.let { state ->
+ state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
+ }
+ }
+
+ return squishedViewState
+ }
+
+ /**
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
* it's not available, it will recreate one by measuring, which may be expensive.
*/
- private fun obtainViewState(state: MediaHostState?): TransitionViewState? {
+ @VisibleForTesting
+ fun obtainViewState(state: MediaHostState?): TransitionViewState? {
if (state == null || state.measurementInput == null) {
return null
}
@@ -291,41 +354,46 @@ class MediaViewController @Inject constructor(
val viewState = viewStates[cacheKey]
if (viewState != null) {
// we already have cached this measurement, let's continue
+ if (state.squishFraction <= 1f) {
+ return squishViewState(viewState, state.squishFraction)
+ }
return viewState
}
// Copy the key since this might call recursively into it and we're using tmpKey
cacheKey = cacheKey.copy()
val result: TransitionViewState?
- if (transitionLayout != null) {
- // Let's create a new measurement
- if (state.expansion == 0.0f || state.expansion == 1.0f) {
- result = transitionLayout!!.calculateViewState(
- state.measurementInput!!,
- constraintSetForExpansion(state.expansion),
- TransitionViewState())
-
- setGutsViewState(result)
- // We don't want to cache interpolated or null states as this could quickly fill up
- // our cache. We only cache the start and the end states since the interpolation
- // is cheap
- viewStates[cacheKey] = result
- } else {
- // This is an interpolated state
- val startState = state.copy().also { it.expansion = 0.0f }
-
- // Given that we have a measurement and a view, let's get (guaranteed) viewstates
- // from the start and end state and interpolate them
- val startViewState = obtainViewState(startState) as TransitionViewState
- val endState = state.copy().also { it.expansion = 1.0f }
- val endViewState = obtainViewState(endState) as TransitionViewState
- result = layoutController.getInterpolatedState(
- startViewState,
- endViewState,
- state.expansion)
- }
+ if (transitionLayout == null) {
+ return null
+ }
+ // Let's create a new measurement
+ if (state.expansion == 0.0f || state.expansion == 1.0f) {
+ result = transitionLayout!!.calculateViewState(
+ state.measurementInput!!,
+ constraintSetForExpansion(state.expansion),
+ TransitionViewState())
+
+ setGutsViewState(result)
+ // We don't want to cache interpolated or null states as this could quickly fill up
+ // our cache. We only cache the start and the end states since the interpolation
+ // is cheap
+ viewStates[cacheKey] = result
} else {
- result = null
+ // This is an interpolated state
+ val startState = state.copy().also { it.expansion = 0.0f }
+
+ // Given that we have a measurement and a view, let's get (guaranteed) viewstates
+ // from the start and end state and interpolate them
+ val startViewState = obtainViewState(startState) as TransitionViewState
+ val endState = state.copy().also { it.expansion = 1.0f }
+ val endViewState = obtainViewState(endState) as TransitionViewState
+ result = layoutController.getInterpolatedState(
+ startViewState,
+ endViewState,
+ state.expansion)
+ }
+ if (state.squishFraction <= 1f) {
+ return squishViewState(result, state.squishFraction)
}
return result
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
index 52ac4e0682a3..8ae75fc34acb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
@@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) {
R.id.media_subtitle2,
R.id.media_subtitle3
)
+
+ val mediaTitlesAndSubtitlesIds = setOf(
+ R.id.media_title1,
+ R.id.media_title2,
+ R.id.media_title3,
+ R.id.media_subtitle1,
+ R.id.media_subtitle2,
+ R.id.media_subtitle3
+ )
+
+ val mediaContainersIds = setOf(
+ R.id.media_cover1_container,
+ R.id.media_cover2_container,
+ R.id.media_cover3_container
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 0b9b32b0d7d7..2a8168b0cb36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -51,9 +51,10 @@ import javax.inject.Inject;
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
*/
@SysUISingleton
-public class RingtonePlayer extends CoreStartable {
+public class RingtonePlayer implements CoreStartable {
private static final String TAG = "RingtonePlayer";
private static final boolean LOGD = false;
+ private final Context mContext;
// TODO: support Uri switching under same IBinder
@@ -64,7 +65,7 @@ public class RingtonePlayer extends CoreStartable {
@Inject
public RingtonePlayer(Context context) {
- super(context);
+ mContext = context;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 53b4d434bfcb..91e7b4933096 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -18,7 +18,6 @@ package com.android.systemui.media.dream;
import static com.android.systemui.flags.Flags.DREAM_MEDIA_COMPLICATION;
-import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -38,7 +37,7 @@ import javax.inject.Inject;
* {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering
* the media complication as appropriate
*/
-public class MediaDreamSentinel extends CoreStartable {
+public class MediaDreamSentinel implements CoreStartable {
private static final String TAG = "MediaDreamSentinel";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -113,11 +112,10 @@ public class MediaDreamSentinel extends CoreStartable {
private final FeatureFlags mFeatureFlags;
@Inject
- public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
+ public MediaDreamSentinel(MediaDataManager mediaDataManager,
DreamOverlayStateController dreamOverlayStateController,
DreamMediaEntryComplication mediaEntryComplication,
FeatureFlags featureFlags) {
- super(context);
mMediaDataManager = mediaDataManager;
mDreamOverlayStateController = dreamOverlayStateController;
mMediaEntryComplication = mediaEntryComplication;
diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
index d60172a17988..0ba5f28c351f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
@@ -40,7 +40,7 @@ import javax.inject.Inject;
* documented at {@link #handleTaskStackChanged} apply.
*/
@SysUISingleton
-public class HomeSoundEffectController extends CoreStartable {
+public class HomeSoundEffectController implements CoreStartable {
private static final String TAG = "HomeSoundEffectController";
private final AudioManager mAudioManager;
@@ -65,7 +65,6 @@ public class HomeSoundEffectController extends CoreStartable {
TaskStackChangeListeners taskStackChangeListeners,
ActivityManagerWrapper activityManagerWrapper,
PackageManager packageManager) {
- super(context);
mAudioManager = audioManager;
mTaskStackChangeListeners = taskStackChangeListeners;
mActivityManagerWrapper = activityManagerWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index f5caefbf4ced..a4a968067462 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -43,7 +43,7 @@ class MediaTttCommandLineHelper @Inject constructor(
private val commandRegistry: CommandRegistry,
private val context: Context,
@Main private val mainExecutor: Executor
-) : CoreStartable(context) {
+) : CoreStartable {
/** All commands for the sender device. */
inner class SenderCommand : Command {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50a10bc0b15a..6da8c69c013b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -114,6 +114,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
@@ -211,6 +212,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final NotificationShadeDepthController mNotificationShadeDepthController;
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
private final UserContextProvider mUserContextProvider;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private final RegionSamplingHelper mRegionSamplingHelper;
private final int mNavColorSampleMargin;
private NavigationBarFrame mFrame;
@@ -451,6 +453,28 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
};
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ private void notifyScreenStateChanged(boolean isScreenOn) {
+ notifyNavigationBarScreenOn();
+ mView.onScreenStateChanged(isScreenOn);
+ }
+
+ @Override
+ public void onStartedWakingUp() {
+ notifyScreenStateChanged(true);
+ if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ }
+ }
+
+ @Override
+ public void onFinishedGoingToSleep() {
+ notifyScreenStateChanged(false);
+ mRegionSamplingHelper.stop();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -491,7 +515,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
NavigationBarTransitions navigationBarTransitions,
EdgeBackGestureHandler edgeBackGestureHandler,
Optional<BackAnimation> backAnimation,
- UserContextProvider userContextProvider) {
+ UserContextProvider userContextProvider,
+ WakefulnessLifecycle wakefulnessLifecycle) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -529,6 +554,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mTelecomManagerOptional = telecomManagerOptional;
mInputMethodManager = inputMethodManager;
mUserContextProvider = userContextProvider;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -682,11 +708,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
prepareNavigationBarView();
checkNavBarModes();
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
Handler.getMain(), UserHandle.ALL);
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -737,6 +762,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
getBarTransitions().destroy();
mOverviewProxyService.removeCallback(mOverviewProxyListener);
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
if (mOrientationHandle != null) {
resetSecondaryHandle();
getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
@@ -1619,19 +1645,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
return;
}
String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)
- || Intent.ACTION_SCREEN_ON.equals(action)) {
- notifyNavigationBarScreenOn();
- boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action);
- mView.onScreenStateChanged(isScreenOn);
- if (isScreenOn) {
- if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
- mRegionSamplingHelper.start(mSamplingBounds);
- }
- } else {
- mRegionSamplingHelper.stop();
- }
- }
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
// The accessibility settings may be different for the new user
updateAccessibilityStateFlags();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 029cf68b243d..3fd1aa73c033 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
import android.content.ContentResolver;
@@ -141,7 +142,13 @@ public class NavigationBarController implements
public void onConfigChanged(Configuration newConfig) {
boolean isOldConfigTablet = mIsTablet;
mIsTablet = isTablet(mContext);
+ boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+ // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+ + " willApplyConfigToNavbars=" + willApplyConfig
+ + " navBarCount=" + mNavigationBars.size());
if (mTaskbarDelegate.isInitialized()) {
mTaskbarDelegate.onConfigurationChanged(newConfig);
}
@@ -150,7 +157,7 @@ public class NavigationBarController implements
return;
}
- if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ if (willApplyConfig) {
for (int i = 0; i < mNavigationBars.size(); i++) {
recreateNavigationBar(mNavigationBars.keyAt(i));
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index a8799c744656..709467ffd3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -113,7 +113,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private static final int MAX_NUM_LOGGED_GESTURES = 10;
static final boolean DEBUG_MISSING_GESTURE = false;
- static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
+ public static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 67dae9e7a0ea..1da866efc08d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -46,6 +46,7 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -59,7 +60,7 @@ import javax.inject.Inject;
import dagger.Lazy;
@SysUISingleton
-public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
+public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -78,6 +79,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
private final PowerManager mPowerManager;
private final WarningsUI mWarnings;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private InattentiveSleepWarningView mOverlayView;
private final Configuration mLastConfiguration = new Configuration();
private int mPlugType = 0;
@@ -103,22 +105,37 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
+ private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onStartedWakingUp() {
+ mScreenOffTime = -1;
+ }
+
+ @Override
+ public void onFinishedGoingToSleep() {
+ mScreenOffTime = SystemClock.elapsedRealtime();
+ }
+ };
@Inject
public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+ WakefulnessLifecycle wakefulnessLifecycle,
PowerManager powerManager) {
- super(context);
+ mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mWarnings = warningsUI;
mEnhancedEstimates = enhancedEstimates;
mPowerManager = powerManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
}
public void start() {
@@ -137,6 +154,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
// Check to see if we need to let the user know that the phone previously shut down due
// to the temperature being too high.
@@ -169,7 +187,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
@@ -232,8 +250,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
// Force get initial values. Relying on Sticky behavior until API for getting info.
@@ -316,10 +332,6 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks {
plugged, bucket);
});
- } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mScreenOffTime = SystemClock.elapsedRealtime();
- } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
- mScreenOffTime = -1;
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
mWarnings.userSwitched();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
index 829077bf2ba4..1c477991211a 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
@@ -70,8 +70,8 @@ import javax.inject.Inject;
* recording audio, camera, the screen, or accessing the location.
*/
@SysUISingleton
-public class TvPrivacyChipsController extends CoreStartable
- implements PrivacyItemController.Callback {
+public class TvPrivacyChipsController
+ implements CoreStartable, PrivacyItemController.Callback {
private static final String TAG = "TvPrivacyChipsController";
private static final boolean DEBUG = false;
@@ -106,6 +106,7 @@ public class TvPrivacyChipsController extends CoreStartable
// How long chips stay expanded after an update.
private static final int EXPANDED_DURATION_MS = 4000;
+ private final Context mContext;
private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
private final Runnable mCollapseRunnable = this::collapseChips;
private final Runnable mUpdatePrivacyItemsRunnable = this::updateChipsAndAnnounce;
@@ -130,7 +131,7 @@ public class TvPrivacyChipsController extends CoreStartable
@Inject
public TvPrivacyChipsController(Context context, PrivacyItemController privacyItemController,
IWindowManager iWindowManager) {
- super(context);
+ mContext = context;
if (DEBUG) Log.d(TAG, "TvPrivacyChipsController running");
mPrivacyItemController = privacyItemController;
mIWindowManager = iWindowManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 7a44058a46c7..498a98b87b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -691,6 +691,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+ // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
+ // and media player expect no change by squishiness in lock screen shade
+ mQsMediaHost.setSquishFraction(1.0F);
+ } else {
+ mQsMediaHost.setSquishFraction(mSquishinessFraction);
+ }
+
}
private void setAlphaAnimationProgress(float progress) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 3e445ddfc2a1..d39368012487 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -36,6 +36,7 @@ import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
@@ -182,6 +183,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements
setBindService(true);
}
+ /**
+ * Binds or unbinds to IQSService
+ */
+ @WorkerThread
public void setBindService(boolean bind) {
if (mBound && mUnbindImmediate) {
// If we are already bound and expecting to unbind, this means we should stay bound
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 11d955580983..d3c06f60bc90 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
@@ -23,7 +23,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.android.settingslib.Utils
-import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -250,22 +249,19 @@ class FooterActionsViewModel(
status: UserSwitcherStatusModel.Enabled
): FooterActionsButtonViewModel {
val icon = status.currentUserImage!!
- val iconTint =
- if (status.isGuestUser && icon !is UserIconDrawable) {
- Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground)
- } else {
- null
- }
return FooterActionsButtonViewModel(
id = R.id.multi_user_switch,
- Icon.Loaded(
- icon,
- ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
- ),
- iconTint,
- R.drawable.qs_footer_action_circle,
- this::onUserSwitcherClicked,
+ icon =
+ Icon.Loaded(
+ icon,
+ ContentDescription.Loaded(
+ userSwitcherContentDescription(status.currentUserName)
+ ),
+ ),
+ iconTint = null,
+ background = R.drawable.qs_footer_action_circle,
+ onClick = this::onUserSwitcherClicked,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 9b3b843c9848..b041f957d771 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -29,13 +29,14 @@ import java.io.PrintWriter;
/**
* A proxy to a Recents implementation.
*/
-public class Recents extends CoreStartable implements CommandQueue.Callbacks {
+public class Recents implements CoreStartable, CommandQueue.Callbacks {
+ private final Context mContext;
private final RecentsImplementation mImpl;
private final CommandQueue mCommandQueue;
public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) {
- super(context);
+ mContext = context;
mImpl = impl;
mCommandQueue = commandQueue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index 950806d89422..ead3b7b1de53 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -49,7 +49,6 @@ public class DraggableConstraintLayout extends ConstraintLayout
private final SwipeDismissHandler mSwipeDismissHandler;
private final GestureDetector mSwipeDetector;
private View mActionsContainer;
- private View mActionsContainerBackground;
private SwipeDismissCallbacks mCallbacks;
private final DisplayMetrics mDisplayMetrics;
@@ -111,6 +110,9 @@ public class DraggableConstraintLayout extends ConstraintLayout
}
});
mSwipeDetector.setIsLongpressEnabled(false);
+
+ mCallbacks = new SwipeDismissCallbacks() {
+ }; // default to unimplemented callbacks
}
public void setCallbacks(SwipeDismissCallbacks callbacks) {
@@ -119,16 +121,13 @@ public class DraggableConstraintLayout extends ConstraintLayout
@Override
public boolean onInterceptHoverEvent(MotionEvent event) {
- if (mCallbacks != null) {
- mCallbacks.onInteraction();
- }
+ mCallbacks.onInteraction();
return super.onInterceptHoverEvent(event);
}
@Override // View
protected void onFinishInflate() {
mActionsContainer = findViewById(R.id.actions_container);
- mActionsContainerBackground = findViewById(R.id.actions_container_background);
}
@Override
@@ -186,6 +185,13 @@ public class DraggableConstraintLayout extends ConstraintLayout
inoutInfo.touchableRegion.set(r);
}
+ private int getBackgroundRight() {
+ // background expected to be null in testing.
+ // animation may have unexpected behavior if view is not present
+ View background = findViewById(R.id.actions_container_background);
+ return background == null ? 0 : background.getRight();
+ }
+
/**
* Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not
* met
@@ -213,8 +219,6 @@ public class DraggableConstraintLayout extends ConstraintLayout
mGestureDetector = new GestureDetector(context, gestureListener);
mDisplayMetrics = new DisplayMetrics();
context.getDisplay().getRealMetrics(mDisplayMetrics);
- mCallbacks = new SwipeDismissCallbacks() {
- }; // default to unimplemented callbacks
}
@Override
@@ -230,7 +234,9 @@ public class DraggableConstraintLayout extends ConstraintLayout
return true;
}
if (isPastDismissThreshold()) {
- dismiss();
+ ValueAnimator anim = createSwipeDismissAnimation();
+ mCallbacks.onSwipeDismissInitiated(anim);
+ dismiss(anim);
} else {
// if we've moved, but not past the threshold, start the return animation
if (DEBUG_DISMISS) {
@@ -295,10 +301,7 @@ public class DraggableConstraintLayout extends ConstraintLayout
}
void dismiss() {
- float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS);
- ValueAnimator anim = createSwipeDismissAnimation(velocityPxPerMs);
- mCallbacks.onSwipeDismissInitiated(anim);
- dismiss(anim);
+ dismiss(createSwipeDismissAnimation());
}
private void dismiss(ValueAnimator animator) {
@@ -323,6 +326,11 @@ public class DraggableConstraintLayout extends ConstraintLayout
mDismissAnimation.start();
}
+ private ValueAnimator createSwipeDismissAnimation() {
+ float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS);
+ return createSwipeDismissAnimation(velocityPxPerMs);
+ }
+
private ValueAnimator createSwipeDismissAnimation(float velocity) {
// velocity is measured in pixels per millisecond
velocity = Math.min(3, Math.max(1, velocity));
@@ -337,7 +345,7 @@ public class DraggableConstraintLayout extends ConstraintLayout
if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) {
finalX = mDisplayMetrics.widthPixels;
} else {
- finalX = -1 * mActionsContainerBackground.getRight();
+ finalX = -1 * getBackgroundRight();
}
float distance = Math.abs(finalX - startX);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 309059fdb9ad..95cc0dcadfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -76,7 +76,7 @@ class RequestProcessor @Inject constructor(
)
} else {
// Create a new request of the same type which includes the top component
- ScreenshotRequest(request.source, request.type, info.component)
+ ScreenshotRequest(request.type, request.source, info.component)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 704e11512b37..6d5121a89241 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -28,6 +28,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
import static java.util.Objects.requireNonNull;
@@ -331,9 +332,7 @@ public class ScreenshotController {
if (DEBUG_UI) {
Log.d(TAG, "Corner timeout hit");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
- mPackageName);
- ScreenshotController.this.dismissScreenshot(false);
+ dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
});
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -361,8 +360,7 @@ public class ScreenshotController {
@Override
public void onReceive(Context context, Intent intent) {
if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
- mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER);
- dismissScreenshot(false);
+ dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
}
}
};
@@ -410,24 +408,20 @@ public class ScreenshotController {
/**
* Clears current screenshot
*/
- void dismissScreenshot(boolean immediate) {
+ void dismissScreenshot(ScreenshotEvent event) {
if (DEBUG_DISMISS) {
- Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")");
+ Log.d(TAG, "dismissScreenshot");
}
// If we're already animating out, don't restart the animation
- // (but do obey an immediate dismissal)
- if (!immediate && mScreenshotView.isDismissing()) {
+ if (mScreenshotView.isDismissing()) {
if (DEBUG_DISMISS) {
Log.v(TAG, "Already dismissing, ignoring duplicate command");
}
return;
}
+ mUiEventLogger.log(event, 0, mPackageName);
mScreenshotHandler.cancelTimeout();
- if (immediate) {
- finishDismiss();
- } else {
- mScreenshotView.animateDismissal();
- }
+ mScreenshotView.animateDismissal();
}
boolean isPendingSharedTransition() {
@@ -500,7 +494,7 @@ public class ScreenshotController {
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
}
- dismissScreenshot(false);
+ dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index a4a59ce52c7a..2176825d8b38 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -89,8 +89,7 @@ public class TakeScreenshotService extends Service {
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
if (!mScreenshot.isPendingSharedTransition()) {
- mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER);
- mScreenshot.dismissScreenshot(false);
+ mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index ad073c073ed8..d450afa59c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -42,11 +42,11 @@ import javax.inject.Inject
@SysUISingleton
class UserFileManagerImpl @Inject constructor(
// Context of system process and system user.
- val context: Context,
+ private val context: Context,
val userManager: UserManager,
val broadcastDispatcher: BroadcastDispatcher,
@Background val backgroundExecutor: DelayableExecutor
-) : UserFileManager, CoreStartable(context) {
+) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
@VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6d76c171b143..e3318127af93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,6 +17,8 @@
package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
@@ -26,8 +28,12 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.NotificationPanelView.DEBUG;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -41,6 +47,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+import static java.lang.Float.isNaN;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -48,6 +56,8 @@ import android.annotation.NonNull;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -73,11 +83,13 @@ import android.transition.TransitionManager;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
@@ -86,6 +98,7 @@ import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -178,6 +191,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -230,8 +244,13 @@ import javax.inject.Inject;
import javax.inject.Provider;
@CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController extends PanelViewController {
+public final class NotificationPanelViewController {
+ public static final String TAG = NotificationPanelView.class.getSimpleName();
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
@@ -262,6 +281,22 @@ public final class NotificationPanelViewController extends PanelViewController {
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
+ private static final int NO_FIXED_DURATION = -1;
+ private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
+ private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
+ /**
+ * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
+ * when flinging. A low value will make it that most flings will reach the maximum overshoot.
+ */
+ private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+ private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final Resources mResources;
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final AmbientState mAmbientState;
+ private final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final SystemClock mSystemClock;
+ private final ShadeLogger mShadeLog;
private final DozeParameters mDozeParameters;
private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
@@ -333,6 +368,28 @@ public final class NotificationPanelViewController extends PanelViewController {
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
private final PanelEventsEmitter mPanelEventsEmitter;
+ private final boolean mVibrateOnOpening;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final LatencyTracker mLatencyTracker;
+ private final DozeLog mDozeLog;
+ /** Whether or not the NotificationPanelView can be expanded or collapsed with a drag. */
+ private final boolean mNotificationsDragEnabled;
+ private final Interpolator mBounceInterpolator;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final ShadeExpansionStateManager mShadeExpansionStateManager;
+ private long mDownTime;
+ private boolean mTouchSlopExceededBeforeDown;
+ private boolean mIsLaunchAnimationRunning;
+ private float mOverExpansion;
+ private CentralSurfaces mCentralSurfaces;
+ private HeadsUpManagerPhone mHeadsUpManager;
+ private float mExpandedHeight = 0;
+ private boolean mTracking;
+ private boolean mHintAnimationRunning;
+ private KeyguardBottomAreaView mKeyguardBottomArea;
+ private boolean mExpanding;
private boolean mSplitShadeEnabled;
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
@@ -706,6 +763,54 @@ public final class NotificationPanelViewController extends PanelViewController {
private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+ private float mMinExpandHeight;
+ private boolean mPanelUpdateWhenAnimatorEnds;
+ private boolean mHasVibratedOnOpen = false;
+ private int mFixedDuration = NO_FIXED_DURATION;
+ /** The overshoot amount when the panel flings open. */
+ private float mPanelFlingOvershootAmount;
+ /** The amount of pixels that we have overexpanded the last time with a gesture. */
+ private float mLastGesturedOverExpansion = -1;
+ /** Whether the current animator is the spring back animation. */
+ private boolean mIsSpringBackAnimation;
+ private boolean mInSplitShade;
+ private float mHintDistance;
+ private float mInitialOffsetOnTouch;
+ private boolean mCollapsedAndHeadsUpOnDown;
+ private float mExpandedFraction = 0;
+ private float mExpansionDragDownAmountPx = 0;
+ private boolean mPanelClosedOnDown;
+ private boolean mHasLayoutedSinceDown;
+ private float mUpdateFlingVelocity;
+ private boolean mUpdateFlingOnLayout;
+ private boolean mClosing;
+ private boolean mTouchSlopExceeded;
+ private int mTrackingPointer;
+ private int mTouchSlop;
+ private float mSlopMultiplier;
+ private boolean mTouchAboveFalsingThreshold;
+ private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
+ private boolean mUpwardsWhenThresholdReached;
+ private boolean mAnimatingOnDown;
+ private boolean mHandlingPointerUp;
+ private ValueAnimator mHeightAnimator;
+ /** Whether an instant expand request is currently pending and we are waiting for layout. */
+ private boolean mInstantExpanding;
+ private boolean mAnimateAfterExpanding;
+ private boolean mIsFlinging;
+ private String mViewName;
+ private float mInitialExpandY;
+ private float mInitialExpandX;
+ private boolean mTouchDisabled;
+ private boolean mInitialTouchFromKeyguard;
+ /** Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */
+ private float mNextCollapseSpeedUpFactor = 1.0f;
+ private boolean mGestureWaitForTouchSlop;
+ private boolean mIgnoreXTouchSlop;
+ private boolean mExpandLatencyTracking;
+ private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+ mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -775,32 +880,73 @@ public final class NotificationPanelViewController extends PanelViewController {
CameraGestureHelper cameraGestureHelper,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor) {
- super(view,
- falsingManager,
- dozeLog,
- keyguardStateController,
- (SysuiStatusBarStateController) statusBarStateController,
- notificationShadeWindowController,
- vibratorHelper,
- statusBarKeyguardViewManager,
- latencyTracker,
- flingAnimationUtilsBuilder.get(),
- statusBarTouchableRegionManager,
- lockscreenGestureLogger,
- shadeExpansionStateManager,
- ambientState,
- interactionJankMonitor,
- shadeLogger,
- systemClock);
+ keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ updateExpandedHeightToMaxHeight();
+ }
+ });
+ mAmbientState = ambientState;
mView = view;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
+ mShadeLog = shadeLogger;
+ TouchHandler touchHandler = createTouchHandler();
+ mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewName = mResources.getResourceName(mView.getId());
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mView.addOnLayoutChangeListener(createLayoutChangeListener());
+ mView.setOnTouchListener(touchHandler);
+ mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+
+ mResources = mView.getResources();
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
+ mFlingAnimationUtils = fauBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsClosing = fauBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsDismissing = fauBuilder
+ .reset()
+ .setMaxLengthSeconds(0.5f)
+ .setSpeedUpFactor(0.6f)
+ .setX2(0.6f)
+ .setY2(0.84f)
+ .build();
+ mLatencyTracker = latencyTracker;
+ mBounceInterpolator = new BounceInterpolator();
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mNotificationsDragEnabled = mResources.getBoolean(
+ R.bool.config_enableNotificationShadeDrag);
mVibratorHelper = vibratorHelper;
+ mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+ mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mInteractionJankMonitor = interactionJankMonitor;
+ mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
mPrivacyDotViewController = privacyDotViewController;
mMetricsLogger = metricsLogger;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -822,7 +968,6 @@ public final class NotificationPanelViewController extends PanelViewController {
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
- mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -838,7 +983,6 @@ public final class NotificationPanelViewController extends PanelViewController {
mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
- mInteractionJankMonitor = interactionJankMonitor;
mSysUiState = sysUiState;
mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -1040,9 +1184,14 @@ public final class NotificationPanelViewController extends PanelViewController {
controller.setup(mNotificationContainerParent));
}
- @Override
- protected void loadDimens() {
- super.loadDimens();
+ @VisibleForTesting
+ void loadDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(this.mView.getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+ mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
+ mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
+ mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1715,11 +1864,10 @@ public final class NotificationPanelViewController extends PanelViewController {
// it's possible that nothing animated, so we replicate the termination
// conditions of panelExpansionChanged here
// TODO(b/200063118): This can likely go away in a future refactor CL.
- getPanelExpansionStateManager().updateState(STATE_CLOSED);
+ getShadeExpansionStateManager().updateState(STATE_CLOSED);
}
}
- @Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canPanelBeCollapsed()) {
return;
@@ -1729,7 +1877,20 @@ public final class NotificationPanelViewController extends PanelViewController {
setQsExpandImmediate(true);
setShowShelfOnly(true);
}
- super.collapse(delayed, speedUpFactor);
+ if (DEBUG) this.logf("collapse: " + this);
+ if (canPanelBeCollapsed()) {
+ cancelHeightAnimator();
+ notifyExpandingStarted();
+
+ // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+ setIsClosing(true);
+ if (delayed) {
+ mNextCollapseSpeedUpFactor = speedUpFactor;
+ this.mView.postDelayed(mFlingCollapseRunnable, 120);
+ } else {
+ fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+ }
+ }
}
private void setQsExpandImmediate(boolean expandImmediate) {
@@ -1749,10 +1910,15 @@ public final class NotificationPanelViewController extends PanelViewController {
setQsExpansion(mQsMinExpansionHeight);
}
- @Override
@VisibleForTesting
- protected void cancelHeightAnimator() {
- super.cancelHeightAnimator();
+ void cancelHeightAnimator() {
+ if (mHeightAnimator != null) {
+ if (mHeightAnimator.isRunning()) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ }
+ mHeightAnimator.cancel();
+ }
+ endClosing();
}
public void cancelAnimation() {
@@ -1820,28 +1986,123 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
public void fling(float vel, boolean expand) {
GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
if (gr != null) {
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
- super.fling(vel, expand);
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
}
- @Override
- protected void flingToHeight(float vel, boolean expand, float target,
+ @VisibleForTesting
+ void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
mHeadsUpTouchHelper.notifyFling(!expand);
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
mNotificationStackScrollLayoutController.setPanelFlinging(true);
- super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ if (target == mExpandedHeight && mOverExpansion == 0.0f) {
+ // We're at the target and didn't fling and there's no overshoot
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsFlinging = true;
+ // we want to perform an overshoot animation when flinging open
+ final boolean addOverscroll =
+ expand
+ && !mInSplitShade // Split shade has its own overscroll logic
+ && mStatusBarStateController.getState() != KEYGUARD
+ && mOverExpansion == 0.0f
+ && vel >= 0;
+ final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
+ float overshootAmount = 0.0f;
+ if (addOverscroll) {
+ // Let's overshoot depending on the amount of velocity
+ overshootAmount = MathUtils.lerp(
+ 0.2f,
+ 1.0f,
+ MathUtils.saturate(vel
+ / (this.mFlingAnimationUtils.getHighVelocityPxPerSecond()
+ * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
+ overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
+ }
+ ValueAnimator animator = createHeightAnimator(target, overshootAmount);
+ if (expand) {
+ if (expandBecauseOfFalsing && vel < 0) {
+ vel = 0;
+ }
+ this.mFlingAnimationUtils.apply(animator, mExpandedHeight,
+ target + overshootAmount * mPanelFlingOvershootAmount, vel,
+ this.mView.getHeight());
+ if (vel == 0) {
+ animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
+ }
+ } else {
+ if (shouldUseDismissingAnimation()) {
+ if (vel == 0) {
+ animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ long duration = (long) (200 + mExpandedHeight / this.mView.getHeight() * 100);
+ animator.setDuration(duration);
+ } else {
+ mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+ this.mView.getHeight());
+ }
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mExpandedHeight, target, vel, this.mView.getHeight());
+ }
+
+ // Make it shorter if we run a canned animation
+ if (vel == 0) {
+ animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+ }
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (!mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (shouldSpringBack && !mCancelled) {
+ // After the shade is flinged open to an overscrolled state, spring back
+ // the shade by reducing section padding to 0.
+ springBack();
+ } else {
+ onFlingEnd(mCancelled);
+ }
+ }
+ });
+ setAnimator(animator);
+ animator.start();
}
- @Override
- protected void onFlingEnd(boolean cancelled) {
- super.onFlingEnd(cancelled);
+ private void onFlingEnd(boolean cancelled) {
+ mIsFlinging = false;
+ // No overshoot when the animation ends
+ setOverExpansionInternal(0, false /* isFromGesture */);
+ setAnimator(null);
+ mKeyguardStateController.notifyPanelFlingEnd();
+ if (!cancelled) {
+ endJankMonitoring();
+ notifyExpandingFinished();
+ } else {
+ cancelJankMonitoring();
+ }
+ updatePanelExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
}
@@ -1936,8 +2197,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return mQsTracking;
}
- @Override
- protected boolean isInContentBounds(float x, float y) {
+ private boolean isInContentBounds(float x, float y) {
float stackScrollerX = mNotificationStackScrollLayoutController.getX();
return !mNotificationStackScrollLayoutController
.isBelowLastNotification(x - stackScrollerX, y)
@@ -2070,9 +2330,8 @@ public final class NotificationPanelViewController extends PanelViewController {
- mQsMinExpansionHeight));
}
- @Override
- protected boolean shouldExpandWhenNotFlinging() {
- if (super.shouldExpandWhenNotFlinging()) {
+ private boolean shouldExpandWhenNotFlinging() {
+ if (getExpandedFraction() > 0.5f) {
return true;
}
if (mAllowExpandForSmallExpansion) {
@@ -2084,8 +2343,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return false;
}
- @Override
- protected float getOpeningHeight() {
+ private float getOpeningHeight() {
return mNotificationStackScrollLayoutController.getOpeningHeight();
}
@@ -2236,9 +2494,20 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- boolean expands = super.flingExpands(vel, vectorVel, x, y);
+ private boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ boolean expands = true;
+ if (!this.mFalsingManager.isUnlockingDisabled()) {
+ @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0
+ ? QUICK_SETTINGS : (
+ mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+ if (!isFalseTouch(x, y, interactionType)) {
+ if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ expands = shouldExpandWhenNotFlinging();
+ } else {
+ expands = vel > 0;
+ }
+ }
+ }
// If we are already running a QS expansion, make sure that we keep the panel open.
if (mQsExpansionAnimator != null) {
@@ -2247,8 +2516,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return expands;
}
- @Override
- protected boolean shouldGestureWaitForTouchSlop() {
+ private boolean shouldGestureWaitForTouchSlop() {
if (mExpectingSynthesizedDown) {
mExpectingSynthesizedDown = false;
return false;
@@ -2326,7 +2594,7 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- protected int getFalsingThreshold() {
+ private int getFalsingThreshold() {
float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
return (int) (mQsFalsingThreshold * factor);
}
@@ -3066,8 +3334,8 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
- protected boolean canCollapsePanelOnTouch() {
+ @VisibleForTesting
+ boolean canCollapsePanelOnTouch() {
if (!isInSettings() && mBarState == KEYGUARD) {
return true;
}
@@ -3079,7 +3347,6 @@ public final class NotificationPanelViewController extends PanelViewController {
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- @Override
public int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
@@ -3113,8 +3380,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return mIsExpanding;
}
- @Override
- protected void onHeightUpdated(float expandedHeight) {
+ private void onHeightUpdated(float expandedHeight) {
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
@@ -3293,9 +3559,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mLockIconViewController.setAlpha(alpha);
}
- @Override
- protected void onExpandingStarted() {
- super.onExpandingStarted();
+ private void onExpandingStarted() {
mNotificationStackScrollLayoutController.onExpansionStarted();
mIsExpanding = true;
mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
@@ -3311,8 +3575,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mQs.setHeaderListening(true);
}
- @Override
- protected void onExpandingFinished() {
+ private void onExpandingFinished() {
mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
@@ -3364,18 +3627,58 @@ public final class NotificationPanelViewController extends PanelViewController {
mQs.setListening(listening);
}
- @Override
public void expand(boolean animate) {
- super.expand(animate);
+ if (isFullyCollapsed() || isCollapsing()) {
+ mInstantExpanding = true;
+ mAnimateAfterExpanding = animate;
+ mUpdateFlingOnLayout = false;
+ abortAnimations();
+ if (mTracking) {
+ // The panel is expanded after this call.
+ onTrackingStopped(true /* expands */);
+ }
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ updatePanelExpansionAndVisibility();
+ // Wait for window manager to pickup the change, so we know the maximum height of the
+ // panel then.
+ this.mView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mInstantExpanding) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+ return;
+ }
+ if (mCentralSurfaces.getNotificationShadeWindowView()
+ .isVisibleToUser()) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+ if (mAnimateAfterExpanding) {
+ notifyExpandingStarted();
+ beginJankMonitoring();
+ fling(0, true /* expand */);
+ } else {
+ setExpandedFraction(1f);
+ }
+ mInstantExpanding = false;
+ }
+ }
+ });
+ // Make sure a layout really happens.
+ this.mView.requestLayout();
+ }
+
setListening(true);
}
- @Override
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
}
- super.setOverExpansion(overExpansion);
+ mOverExpansion = overExpansion;
// Translating the quick settings by half the overexpansion to center it in the background
// frame
updateQsFrameTranslation();
@@ -3383,14 +3686,18 @@ public final class NotificationPanelViewController extends PanelViewController {
}
private void updateQsFrameTranslation() {
- mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, mOverExpansion,
- mQsTranslationForFullShadeTransition);
+ mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs,
+ mNavigationBarBottomHeight + mAmbientState.getStackTopMargin());
+
}
- @Override
- protected void onTrackingStarted() {
+ private void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
- super.onTrackingStarted();
+ endClosing();
+ mTracking = true;
+ mCentralSurfaces.onTrackingStarted();
+ notifyExpandingStarted();
+ updatePanelExpansionAndVisibility();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
setQsExpandImmediate(true);
@@ -3400,10 +3707,11 @@ public final class NotificationPanelViewController extends PanelViewController {
cancelPendingPanelCollapse();
}
- @Override
- protected void onTrackingStopped(boolean expand) {
+ private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
- super.onTrackingStopped(expand);
+ mTracking = false;
+ mCentralSurfaces.onTrackingStopped(expand);
+ updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
true /* animate */);
@@ -3420,37 +3728,48 @@ public final class NotificationPanelViewController extends PanelViewController {
getHeight(), mNavigationBarBottomHeight);
}
- @Override
- protected void startUnlockHintAnimation() {
+ @VisibleForTesting
+ void startUnlockHintAnimation() {
if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) {
onUnlockHintStarted();
onUnlockHintFinished();
return;
}
- super.startUnlockHintAnimation();
+
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHeightAnimator != null || mTracking) {
+ return;
+ }
+ notifyExpandingStarted();
+ startUnlockHintAnimationPhase1(() -> {
+ notifyExpandingFinished();
+ onUnlockHintFinished();
+ mHintAnimationRunning = false;
+ });
+ onUnlockHintStarted();
+ mHintAnimationRunning = true;
}
- @Override
- protected void onUnlockHintFinished() {
- super.onUnlockHintFinished();
+ @VisibleForTesting
+ void onUnlockHintFinished() {
+ mCentralSurfaces.onHintFinished();
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
- @Override
- protected void onUnlockHintStarted() {
- super.onUnlockHintStarted();
+ @VisibleForTesting
+ void onUnlockHintStarted() {
+ mCentralSurfaces.onUnlockHintStarted();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
- @Override
- protected boolean shouldUseDismissingAnimation() {
+ private boolean shouldUseDismissingAnimation() {
return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
|| !isTracking());
}
- @Override
public int getMaxPanelTransitionDistance() {
// Traditionally the value is based on the number of notifications. On split-shade, we want
// the required distance to be a specific and constant value, to make sure the expansion
@@ -3475,8 +3794,8 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
- protected boolean isTrackingBlocked() {
+ @VisibleForTesting
+ boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3498,19 +3817,17 @@ public final class NotificationPanelViewController extends PanelViewController {
return mIsLaunchTransitionFinished;
}
- @Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
- super.setIsLaunchAnimationRunning(running);
+ mIsLaunchAnimationRunning = running;
if (wasRunning != mIsLaunchAnimationRunning) {
mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
}
}
- @Override
- protected void setIsClosing(boolean isClosing) {
+ private void setIsClosing(boolean isClosing) {
boolean wasClosing = isClosing();
- super.setIsClosing(isClosing);
+ mClosing = isClosing;
if (wasClosing != isClosing) {
mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
}
@@ -3523,7 +3840,6 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
public boolean isDozing() {
return mDozing;
}
@@ -3540,8 +3856,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.dozeTimeTick();
}
- @Override
- protected boolean onMiddleClicked() {
+ private boolean onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3600,15 +3915,13 @@ public final class NotificationPanelViewController extends PanelViewController {
updateVisibility();
}
- @Override
- protected boolean shouldPanelBeVisible() {
+ private boolean shouldPanelBeVisible() {
boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
return headsUpVisible || isExpanded() || mBouncerShowing;
}
- @Override
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- super.setHeadsUpManager(headsUpManager);
+ mHeadsUpManager = headsUpManager;
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -3622,8 +3935,7 @@ public final class NotificationPanelViewController extends PanelViewController {
// otherwise we update the state when the expansion is finished
}
- @Override
- protected void onClosingFinished() {
+ private void onClosingFinished() {
mCentralSurfaces.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
@@ -3712,8 +4024,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mCentralSurfaces.clearNotificationEffects();
}
- @Override
- protected boolean isPanelVisibleBecauseOfHeadsUp() {
+ private boolean isPanelVisibleBecauseOfHeadsUp() {
return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
&& mBarState == StatusBarState.SHADE;
}
@@ -3828,9 +4139,15 @@ public final class NotificationPanelViewController extends PanelViewController {
mNotificationBoundsAnimationDelay = delay;
}
- @Override
public void setTouchAndAnimationDisabled(boolean disabled) {
- super.setTouchAndAnimationDisabled(disabled);
+ mTouchDisabled = disabled;
+ if (mTouchDisabled) {
+ cancelHeightAnimator();
+ if (mTracking) {
+ onTrackingStopped(true /* expanded */);
+ }
+ notifyExpandingFinished();
+ }
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -4030,9 +4347,14 @@ public final class NotificationPanelViewController extends PanelViewController {
mBlockingExpansionForCurrentTouch = mTracking;
}
- @Override
public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
+ pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+ + " tracking=%s timeAnim=%s%s "
+ + "touchDisabled=%s" + "]",
+ this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
+ mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
+ ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
+ mTouchDisabled ? "T" : "f"));
IndentingPrintWriter ipw = asIndenting(pw);
ipw.increaseIndent();
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
@@ -4175,126 +4497,13 @@ public final class NotificationPanelViewController extends PanelViewController {
mConfigurationListener.onThemeChanged();
}
- @Override
- protected OnLayoutChangeListener createLayoutChangeListener() {
- return new OnLayoutChangeListenerImpl();
+ private OnLayoutChangeListener createLayoutChangeListener() {
+ return new OnLayoutChangeListener();
}
- @Override
- protected TouchHandler createTouchHandler() {
- return new TouchHandler() {
-
- private long mLastTouchDownTime = -1L;
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (SPEW_LOGCAT) {
- Log.v(TAG,
- "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
- + "," + event.getY() + ")");
- }
- if (mQs.disallowPanelTouches()) {
- return false;
- }
- initDownStates(event);
- // Do not let touches go to shade or QS if the bouncer is visible,
- // but still let user swipe down to expand the panel, dismissing the bouncer.
- if (mCentralSurfaces.isBouncerShowing()) {
- return true;
- }
- if (mCommandQueue.panelsEnabled()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- return true;
- }
- if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
- return true;
- }
-
- if (!isFullyCollapsed() && onQsIntercept(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
- return true;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- if (event.getDownTime() == mLastTouchDownTime) {
- // An issue can occur when swiping down after unlock, where multiple down
- // events are received in this handler with identical downTimes. Until the
- // source of the issue can be located, detect this case and ignore.
- // see b/193350347
- Log.w(TAG, "Duplicate down event detected... ignoring");
- return true;
- }
- mLastTouchDownTime = event.getDownTime();
- }
-
-
- if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
- return false;
- }
-
- // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
- // otherwise user would be able to pull down QS or expand the shade.
- if (mCentralSurfaces.isBouncerShowingScrimmed()
- || mCentralSurfaces.isBouncerShowingOverDream()) {
- return false;
- }
-
- // Make sure the next touch won't the blocked after the current ends.
- if (event.getAction() == MotionEvent.ACTION_UP
- || event.getAction() == MotionEvent.ACTION_CANCEL) {
- mBlockingExpansionForCurrentTouch = false;
- }
- // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
- // without any ACTION_MOVE event.
- // In such case, simply expand the panel instead of being stuck at the bottom bar.
- if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
- expand(true /* animate */);
- }
- initDownStates(event);
-
- // If pulse is expanding already, let's give it the touch. There are situations
- // where the panel starts expanding even though we're also pulsing
- boolean pulseShouldGetTouch = (!mIsExpanding
- && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
- || mPulseExpansionHandler.isExpanding();
- if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
- // We're expanding all the other ones shouldn't get this anymore
- mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
- return true;
- }
- if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- }
- boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
-
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- return true;
- }
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- handled = true;
- }
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
- && mKeyguardStateController.isShowing()) {
- mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
- }
-
- handled |= super.onTouch(v, event);
- return !mDozing || mPulsing || handled;
- }
- };
+ @VisibleForTesting
+ TouchHandler createTouchHandler() {
+ return new TouchHandler();
}
private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
@@ -4346,8 +4555,7 @@ public final class NotificationPanelViewController extends PanelViewController {
}
};
- @Override
- protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ private OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
@@ -4409,6 +4617,593 @@ public final class NotificationPanelViewController extends PanelViewController {
.commitUpdate(mDisplayId);
}
+ private void logf(String fmt, Object... args) {
+ Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
+
+ private void notifyExpandingStarted() {
+ if (!mExpanding) {
+ mExpanding = true;
+ onExpandingStarted();
+ }
+ }
+
+ private void notifyExpandingFinished() {
+ endClosing();
+ if (mExpanding) {
+ mExpanding = false;
+ onExpandingFinished();
+ }
+ }
+
+ private float getTouchSlop(MotionEvent event) {
+ // Adjust the touch slop if another gesture may be being performed.
+ return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+ ? mTouchSlop * mSlopMultiplier
+ : mTouchSlop;
+ }
+
+ private void addMovement(MotionEvent event) {
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ /** If the latency tracker is enabled, begins tracking expand latency. */
+ public void startExpandLatencyTracking() {
+ if (mLatencyTracker.isEnabled()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
+ mExpandLatencyTracking = true;
+ }
+ }
+
+ private void startOpening(MotionEvent event) {
+ updatePanelExpansionAndVisibility();
+ // Reset at start so haptic can be triggered as soon as panel starts to open.
+ mHasVibratedOnOpen = false;
+ //TODO: keyguard opens QS a different way; log that too?
+
+ // Log the position of the swipe that opened the panel
+ float width = mCentralSurfaces.getDisplayWidth();
+ float height = mCentralSurfaces.getDisplayHeight();
+ int rot = mCentralSurfaces.getRotation();
+
+ mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+ (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+ mLockscreenGestureLogger
+ .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
+ }
+
+ /**
+ * Maybe vibrate as panel is opened.
+ *
+ * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
+ * being opened programmatically (such as by the open panel gesture), we always play haptic.
+ */
+ private void maybeVibrateOnOpening(boolean openingWithTouch) {
+ if (mVibrateOnOpening) {
+ if (!openingWithTouch || !mHasVibratedOnOpen) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ mHasVibratedOnOpen = true;
+ }
+ }
+ }
+
+ /**
+ * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+ * horizontal direction
+ */
+ private boolean isDirectionUpwards(float x, float y) {
+ float xDiff = x - mInitialExpandX;
+ float yDiff = y - mInitialExpandY;
+ if (yDiff >= 0) {
+ return false;
+ }
+ return Math.abs(yDiff) >= Math.abs(xDiff);
+ }
+
+ /** Called when a MotionEvent is about to trigger Shade expansion. */
+ public void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialExpandY = newY;
+ mInitialExpandX = newX;
+ mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ setExpandedHeight(mInitialOffsetOnTouch);
+ onTrackingStarted();
+ }
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ mAmbientState.setSwipingUp(false);
+ if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+ || Math.abs(y - mInitialExpandY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float vel = mVelocityTracker.getYVelocity();
+ float vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ final boolean onKeyguard = mKeyguardStateController.isShowing();
+ final boolean expand;
+ if (mKeyguardStateController.isKeyguardFadingAway()
+ || (mInitialTouchFromKeyguard && !onKeyguard)) {
+ // Don't expand for any touches that started from the keyguard and ended after the
+ // keyguard is gone.
+ expand = false;
+ } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ if (onKeyguard) {
+ expand = true;
+ } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
+ expand = false;
+ } else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
+ expand = !mPanelClosedOnDown;
+ }
+ } else {
+ expand = flingExpands(vel, vectorVel, x, y);
+ }
+
+ mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+ mCentralSurfaces.isFalsingThresholdNeeded(),
+ mCentralSurfaces.isWakeUpComingFromTouch());
+ // Log collapse gesture if on lock screen.
+ if (!expand && onKeyguard) {
+ float displayDensity = mCentralSurfaces.getDisplayDensity();
+ int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
+ int velocityDp = (int) Math.abs(vel / displayDensity);
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
+ }
+ @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
+ : y - mInitialExpandY > 0 ? QUICK_SETTINGS
+ : (mKeyguardStateController.canDismissLockScreen()
+ ? UNLOCK : BOUNCER_UNLOCK);
+
+ fling(vel, expand, isFalseTouch(x, y, interactionType));
+ onTrackingStopped(expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else if (!mCentralSurfaces.isBouncerShowing()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mKeyguardStateController.isKeyguardGoingAway()) {
+ boolean expands = onEmptySpaceClick();
+ onTrackingStopped(expands);
+ }
+ mVelocityTracker.clear();
+ }
+
+ private float getCurrentExpandVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ return mVelocityTracker.getYVelocity();
+ }
+
+ private void endClosing() {
+ if (mClosing) {
+ setIsClosing(false);
+ onClosingFinished();
+ }
+ }
+
+ /**
+ * @param x the final x-coordinate when the finger was lifted
+ * @param y the final y-coordinate when the finger was lifted
+ * @return whether this motion should be regarded as a false touch
+ */
+ private boolean isFalseTouch(float x, float y,
+ @Classifier.InteractionType int interactionType) {
+ if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
+ return false;
+ }
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch(interactionType);
+ }
+ if (!mTouchAboveFalsingThreshold) {
+ return true;
+ }
+ if (mUpwardsWhenThresholdReached) {
+ return false;
+ }
+ return !isDirectionUpwards(x, y);
+ }
+
+ private void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+ }
+
+ private void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+ boolean expandBecauseOfFalsing) {
+ float target = expand ? getMaxPanelHeight() : 0;
+ if (!expand) {
+ setIsClosing(true);
+ }
+ flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+ private void springBack() {
+ if (mOverExpansion == 0) {
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsSpringBackAnimation = true;
+ ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
+ animator.addUpdateListener(
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+ false /* isFromGesture */));
+ animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsSpringBackAnimation = false;
+ onFlingEnd(mCancelled);
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ public String getName() {
+ return mViewName;
+ }
+
+ @VisibleForTesting
+ void setExpandedHeight(float height) {
+ if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ setExpandedHeightInternal(height);
+ }
+
+ private void updateExpandedHeightToMaxHeight() {
+ float currentMaxPanelHeight = getMaxPanelHeight();
+
+ if (isFullyCollapsed()) {
+ return;
+ }
+
+ if (currentMaxPanelHeight == mExpandedHeight) {
+ return;
+ }
+
+ if (mTracking && !isTrackingBlocked()) {
+ return;
+ }
+
+ if (mHeightAnimator != null && !mIsSpringBackAnimation) {
+ mPanelUpdateWhenAnimatorEnds = true;
+ return;
+ }
+
+ setExpandedHeight(currentMaxPanelHeight);
+ }
+
+ private void setExpandedHeightInternal(float h) {
+ if (isNaN(h)) {
+ Log.wtf(TAG, "ExpandedHeight set to NaN");
+ }
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(
+ () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
+ }
+ float maxPanelHeight = getMaxPanelTransitionDistance();
+ if (mHeightAnimator == null) {
+ // Split shade has its own overscroll logic
+ if (mTracking && !mInSplitShade) {
+ float overExpansionPixels = Math.max(0, h - maxPanelHeight);
+ setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+ }
+ }
+ mExpandedHeight = Math.min(h, maxPanelHeight);
+ // If we are closing the panel and we are almost there due to a slow decelerating
+ // interpolator, abort the animation.
+ if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+ mExpandedHeight = 0f;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.end();
+ }
+ }
+ mExpansionDragDownAmountPx = h;
+ mExpandedFraction = Math.min(1f,
+ maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ mAmbientState.setExpansionFraction(mExpandedFraction);
+ onHeightUpdated(mExpandedHeight);
+ updatePanelExpansionAndVisibility();
+ });
+ }
+
+ /**
+ * Set the current overexpansion
+ *
+ * @param overExpansion the amount of overexpansion to apply
+ * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
+ */
+ private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
+ if (!isFromGesture) {
+ mLastGesturedOverExpansion = -1;
+ setOverExpansion(overExpansion);
+ } else if (mLastGesturedOverExpansion != overExpansion) {
+ mLastGesturedOverExpansion = overExpansion;
+ final float heightForFullOvershoot = mView.getHeight() / 3.0f;
+ float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
+ newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
+ setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
+ }
+ }
+
+ /** Sets the expanded height relative to a number from 0 to 1. */
+ public void setExpandedFraction(float frac) {
+ setExpandedHeight(getMaxPanelTransitionDistance() * frac);
+ }
+
+ @VisibleForTesting
+ float getExpandedHeight() {
+ return mExpandedHeight;
+ }
+
+ public float getExpandedFraction() {
+ return mExpandedFraction;
+ }
+
+ public boolean isFullyExpanded() {
+ return mExpandedHeight >= getMaxPanelHeight();
+ }
+
+ public boolean isFullyCollapsed() {
+ return mExpandedFraction <= 0.0f;
+ }
+
+ public boolean isCollapsing() {
+ return mClosing || mIsLaunchAnimationRunning;
+ }
+
+ public boolean isFlinging() {
+ return mIsFlinging;
+ }
+
+ public boolean isTracking() {
+ return mTracking;
+ }
+
+ /** Returns whether the shade can be collapsed. */
+ public boolean canPanelBeCollapsed() {
+ return !isFullyCollapsed() && !mTracking && !mClosing;
+ }
+
+ /** Collapses the shade instantly without animation. */
+ public void instantCollapse() {
+ abortAnimations();
+ setExpandedFraction(0f);
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ if (mInstantExpanding) {
+ mInstantExpanding = false;
+ updatePanelExpansionAndVisibility();
+ }
+ }
+
+ private void abortAnimations() {
+ cancelHeightAnimator();
+ mView.removeCallbacks(mFlingCollapseRunnable);
+ }
+
+ public boolean isUnlockHintRunning() {
+ return mHintAnimationRunning;
+ }
+
+ /**
+ * Phase 1: Move everything upwards.
+ */
+ private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+ float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+ ValueAnimator animator = createHeightAnimator(target);
+ animator.setDuration(250);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ } else {
+ startUnlockHintAnimationPhase2(onAnimationFinished);
+ }
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+
+ final List<ViewPropertyAnimator> indicationAnimators =
+ mKeyguardBottomArea.getIndicationAreaAnimators();
+ for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+ indicationAreaAnimator
+ .translationY(-mHintDistance)
+ .setDuration(250)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> indicationAreaAnimator
+ .translationY(0)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start())
+ .start();
+ }
+ }
+
+ private void setAnimator(ValueAnimator animator) {
+ mHeightAnimator = animator;
+ if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ updateExpandedHeightToMaxHeight();
+ }
+ }
+
+ /**
+ * Phase 2: Bounce down.
+ */
+ private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+ ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+ animator.setDuration(450);
+ animator.setInterpolator(mBounceInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ updatePanelExpansionAndVisibility();
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+ }
+
+ private ValueAnimator createHeightAnimator(float targetHeight) {
+ return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
+ }
+
+ /**
+ * Create an animator that can also overshoot
+ *
+ * @param targetHeight the target height
+ * @param overshootAmount the amount of overshoot desired
+ */
+ private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
+ float startExpansion = mOverExpansion;
+ ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+ animator.addUpdateListener(
+ animation -> {
+ if (overshootAmount > 0.0f
+ // Also remove the overExpansion when collapsing
+ || (targetHeight == 0.0f && startExpansion != 0)) {
+ final float expansion = MathUtils.lerp(
+ startExpansion,
+ mPanelFlingOvershootAmount * overshootAmount,
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ animator.getAnimatedFraction()));
+ setOverExpansionInternal(expansion, false /* isFromGesture */);
+ }
+ setExpandedHeightInternal((float) animation.getAnimatedValue());
+ });
+ return animator;
+ }
+
+ /** Update the visibility of {@link NotificationPanelView} if necessary. */
+ private void updateVisibility() {
+ mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+ }
+
+ /**
+ * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
+ *
+ * TODO(b/200063118): Could public calls to this method be replaced with calls to
+ * {@link #updateVisibility()}? That would allow us to make this method private.
+ */
+ public void updatePanelExpansionAndVisibility() {
+ mShadeExpansionStateManager.onPanelExpansionChanged(
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ updateVisibility();
+ }
+
+ public boolean isExpanded() {
+ return mExpandedFraction > 0f
+ || mInstantExpanding
+ || isPanelVisibleBecauseOfHeadsUp()
+ || mTracking
+ || mHeightAnimator != null
+ && !mIsSpringBackAnimation;
+ }
+
+ /**
+ * Gets called when the user performs a click anywhere in the empty area of the panel.
+ *
+ * @return whether the panel will be expanded after the action performed by this method
+ */
+ private boolean onEmptySpaceClick() {
+ if (mHintAnimationRunning) {
+ return true;
+ }
+ return onMiddleClicked();
+ }
+
+ @VisibleForTesting
+ boolean isClosing() {
+ return mClosing;
+ }
+
+ /** Collapses the shade with an animation duration in milliseconds. */
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
+
+ /** Returns the NotificationPanelView. */
+ public ViewGroup getView() {
+ // TODO: remove this method, or at least reduce references to it.
+ return mView;
+ }
+
+ private void beginJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mView)
+ .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
+ mInteractionJankMonitor.begin(builder);
+ }
+
+ private void endJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ private void cancelJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ private float getExpansionFraction() {
+ return mExpandedFraction;
+ }
+
+ private ShadeExpansionStateManager getShadeExpansionStateManager() {
+ return mShadeExpansionStateManager;
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -4817,13 +5612,18 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- private class OnLayoutChangeListenerImpl extends OnLayoutChangeListener {
-
+ private final class OnLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
- super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
+ updateExpandedHeightToMaxHeight();
+ mHasLayoutedSinceDown = true;
+ if (mUpdateFlingOnLayout) {
+ abortAnimations();
+ fling(mUpdateFlingVelocity, true /* expands */);
+ mUpdateFlingOnLayout = false;
+ }
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
@@ -5081,4 +5881,361 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
}
+
+ /** Handles MotionEvents for the Shade. */
+ public final class TouchHandler implements View.OnTouchListener {
+ private long mLastTouchDownTime = -1L;
+
+ /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (SPEW_LOGCAT) {
+ Log.v(TAG,
+ "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
+ + "," + event.getY() + ")");
+ }
+ if (mQs.disallowPanelTouches()) {
+ return false;
+ }
+ initDownStates(event);
+ // Do not let touches go to shade or QS if the bouncer is visible,
+ // but still let user swipe down to expand the panel, dismissing the bouncer.
+ if (mCentralSurfaces.isBouncerShowing()) {
+ return true;
+ }
+ if (mCommandQueue.panelsEnabled()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ return true;
+ }
+ if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ return true;
+ }
+
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
+ if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+ return true;
+ }
+ if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ return false;
+ }
+
+ /* If the user drags anywhere inside the panel we intercept it if the movement is
+ upwards. This allows closing the shade from anywhere inside the panel.
+ We only do this if the current content is scrolled to the bottom, i.e.
+ canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
+ gesture possible. */
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ boolean canCollapsePanel = canCollapsePanelOnTouch();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mCentralSurfaces.userActivity();
+ mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
+ mMinExpandHeight = 0.0f;
+ mDownTime = mSystemClock.uptimeMillis();
+ if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
+ cancelHeightAnimator();
+ mTouchSlopExceeded = true;
+ return true;
+ }
+ mInitialExpandY = y;
+ mInitialExpandX = x;
+ mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+ mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+ mMotionAborted = false;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mCollapsedAndHeadsUpOnDown = false;
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mTouchAboveFalsingThreshold = false;
+ addMovement(event);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialExpandX = event.getX(newIndex);
+ mInitialExpandY = event.getY(newIndex);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ mVelocityTracker.clear();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialExpandY;
+ addMovement(event);
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
+ float hAbs = Math.abs(h);
+ float touchSlop = getTouchSlop(event);
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
+ && hAbs > Math.abs(x - mInitialExpandX)) {
+ cancelHeightAnimator();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mVelocityTracker.clear();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getDownTime() == mLastTouchDownTime) {
+ // An issue can occur when swiping down after unlock, where multiple down
+ // events are received in this handler with identical downTimes. Until the
+ // source of the issue can be located, detect this case and ignore.
+ // see b/193350347
+ Log.w(TAG, "Duplicate down event detected... ignoring");
+ return true;
+ }
+ mLastTouchDownTime = event.getDownTime();
+ }
+
+
+ if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
+ return false;
+ }
+
+ // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // otherwise user would be able to pull down QS or expand the shade.
+ if (mCentralSurfaces.isBouncerShowingScrimmed()
+ || mCentralSurfaces.isBouncerShowingOverDream()) {
+ return false;
+ }
+
+ // Make sure the next touch won't the blocked after the current ends.
+ if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ mBlockingExpansionForCurrentTouch = false;
+ }
+ // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+ // without any ACTION_MOVE event.
+ // In such case, simply expand the panel instead of being stuck at the bottom bar.
+ if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true /* animate */);
+ }
+ initDownStates(event);
+
+ // If pulse is expanding already, let's give it the touch. There are situations
+ // where the panel starts expanding even though we're also pulsing
+ boolean pulseShouldGetTouch = (!mIsExpanding
+ && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
+ || mPulseExpansionHandler.isExpanding();
+ if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
+ // We're expanding all the other ones shouldn't get this anymore
+ mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
+ return true;
+ }
+ if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ }
+ boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
+
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ return true;
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ handled = true;
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
+ && mKeyguardStateController.isShowing()) {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
+ }
+
+ handled |= handleTouch(event);
+ return !mDozing || mPulsing || handled;
+ }
+
+ private boolean handleTouch(MotionEvent event) {
+ if (mInstantExpanding) {
+ mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+ return false;
+ }
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+ return false;
+ }
+ if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
+ return false;
+ }
+
+ // If dragging should not expand the notifications shade, then return false.
+ if (!mNotificationsDragEnabled) {
+ if (mTracking) {
+ // Turn off tracking if it's on or the shade can get stuck in the down position.
+ onTrackingStopped(true /* expand */);
+ }
+ mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
+ return false;
+ }
+
+ // On expanding, single mouse click expands the panel instead of dragging.
+ if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true);
+ }
+ return true;
+ }
+
+ /*
+ * We capture touch events here and update the expand height here in case according to
+ * the users fingers. This also handles multi-touch.
+ *
+ * Flinging is also enabled in order to open or close the shade.
+ */
+
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+ mIgnoreXTouchSlop = true;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ mMinExpandHeight = 0.0f;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
+ mDownTime = mSystemClock.uptimeMillis();
+ mTouchAboveFalsingThreshold = false;
+ mCollapsedAndHeadsUpOnDown =
+ isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
+ addMovement(event);
+ boolean regularHeightAnimationRunning = mHeightAnimator != null
+ && !mHintAnimationRunning && !mIsSpringBackAnimation;
+ if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
+ mTouchSlopExceeded = regularHeightAnimationRunning
+ || mTouchSlopExceededBeforeDown;
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+ && !mCentralSurfaces.isBouncerShowing()) {
+ startOpening(event);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ mHandlingPointerUp = true;
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ mHandlingPointerUp = false;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ addMovement(event);
+ if (!isFullyCollapsed()) {
+ maybeVibrateOnOpening(true /* openingWithTouch */);
+ }
+ float h = y - mInitialExpandY;
+
+ // If the panel was collapsed when touching, we only need to check for the
+ // y-component of the gesture, as we have no conflicting horizontal gesture.
+ if (Math.abs(h) > getTouchSlop(event)
+ && (Math.abs(h) > Math.abs(x - mInitialExpandX)
+ || mIgnoreXTouchSlop)) {
+ mTouchSlopExceeded = true;
+ if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+ if (mInitialOffsetOnTouch != 0f) {
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ h = 0;
+ }
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ }
+ float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+ newHeight = Math.max(newHeight, mMinExpandHeight);
+ if (-h >= getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
+ }
+ if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+ // Count h==0 as part of swipe-up,
+ // otherwise {@link NotificationStackScrollLayout}
+ // wrongly enables stack height updates at the start of lockscreen swipe-up
+ mAmbientState.setSwipingUp(h <= 0);
+ setExpandedHeightInternal(newHeight);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ addMovement(event);
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
+ if (mHeightAnimator == null) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ endJankMonitoring();
+ } else {
+ cancelJankMonitoring();
+ }
+ }
+ break;
+ }
+ return !mGestureWaitForTouchSlop || mTracking;
+ }
+ }
+
+ /** Listens for config changes. */
+ public class OnConfigurationChangedListener implements
+ NotificationPanelView.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ loadDimens();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
deleted file mode 100644
index fa51d854c9aa..000000000000
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ /dev/null
@@ -1,1493 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade;
-
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
-import static com.android.systemui.classifier.Classifier.GENERIC;
-import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
-import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.NotificationPanelView.DEBUG;
-
-import static java.lang.Float.isNaN;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.classifier.Classifier;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.phone.BounceInterpolator;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.io.PrintWriter;
-import java.util.List;
-
-public abstract class PanelViewController {
- public static final String TAG = NotificationPanelView.class.getSimpleName();
- public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_SPEED_UP_FACTOR = 0.6f;
- public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
- private static final int NO_FIXED_DURATION = -1;
- private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
- private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
-
- /**
- * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
- * when flinging. A low value will make it that most flings will reach the maximum overshoot.
- */
- private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
-
- protected long mDownTime;
- protected boolean mTouchSlopExceededBeforeDown;
- private float mMinExpandHeight;
- private boolean mPanelUpdateWhenAnimatorEnds;
- private final boolean mVibrateOnOpening;
- private boolean mHasVibratedOnOpen = false;
- protected boolean mIsLaunchAnimationRunning;
- private int mFixedDuration = NO_FIXED_DURATION;
- protected float mOverExpansion;
-
- /**
- * The overshoot amount when the panel flings open
- */
- private float mPanelFlingOvershootAmount;
-
- /**
- * The amount of pixels that we have overexpanded the last time with a gesture
- */
- private float mLastGesturedOverExpansion = -1;
-
- /**
- * Is the current animator the spring back animation?
- */
- private boolean mIsSpringBackAnimation;
-
- private boolean mInSplitShade;
-
- private void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
- }
-
- protected CentralSurfaces mCentralSurfaces;
- protected HeadsUpManagerPhone mHeadsUpManager;
- protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-
- private float mHintDistance;
- private float mInitialOffsetOnTouch;
- private boolean mCollapsedAndHeadsUpOnDown;
- private float mExpandedFraction = 0;
- private float mExpansionDragDownAmountPx = 0;
- protected float mExpandedHeight = 0;
- private boolean mPanelClosedOnDown;
- private boolean mHasLayoutedSinceDown;
- private float mUpdateFlingVelocity;
- private boolean mUpdateFlingOnLayout;
- private boolean mClosing;
- protected boolean mTracking;
- private boolean mTouchSlopExceeded;
- private int mTrackingPointer;
- private int mTouchSlop;
- private float mSlopMultiplier;
- protected boolean mHintAnimationRunning;
- private boolean mTouchAboveFalsingThreshold;
- private boolean mTouchStartedInEmptyArea;
- private boolean mMotionAborted;
- private boolean mUpwardsWhenThresholdReached;
- private boolean mAnimatingOnDown;
- private boolean mHandlingPointerUp;
-
- private ValueAnimator mHeightAnimator;
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
- private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
- private final LatencyTracker mLatencyTracker;
- private final FalsingManager mFalsingManager;
- private final DozeLog mDozeLog;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * Whether an instant expand request is currently pending and we are just waiting for layout.
- */
- private boolean mInstantExpanding;
- private boolean mAnimateAfterExpanding;
- private boolean mIsFlinging;
-
- private String mViewName;
- private float mInitialExpandY;
- private float mInitialExpandX;
- private boolean mTouchDisabled;
- private boolean mInitialTouchFromKeyguard;
-
- /**
- * Whether or not the NotificationPanelView can be expanded or collapsed with a drag.
- */
- private final boolean mNotificationsDragEnabled;
-
- private final Interpolator mBounceInterpolator;
- protected KeyguardBottomAreaView mKeyguardBottomArea;
-
- /**
- * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
- */
- private float mNextCollapseSpeedUpFactor = 1.0f;
-
- protected boolean mExpanding;
- private boolean mGestureWaitForTouchSlop;
- private boolean mIgnoreXTouchSlop;
- private boolean mExpandLatencyTracking;
- private final NotificationPanelView mView;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- protected final Resources mResources;
- protected final KeyguardStateController mKeyguardStateController;
- protected final SysuiStatusBarStateController mStatusBarStateController;
- protected final AmbientState mAmbientState;
- protected final LockscreenGestureLogger mLockscreenGestureLogger;
- private final ShadeExpansionStateManager mShadeExpansionStateManager;
- private final InteractionJankMonitor mInteractionJankMonitor;
- protected final SystemClock mSystemClock;
-
- protected final ShadeLogger mShadeLog;
-
- protected abstract void onExpandingFinished();
-
- protected void onExpandingStarted() {
- }
-
- protected void notifyExpandingStarted() {
- if (!mExpanding) {
- mExpanding = true;
- onExpandingStarted();
- }
- }
-
- protected final void notifyExpandingFinished() {
- endClosing();
- if (mExpanding) {
- mExpanding = false;
- onExpandingFinished();
- }
- }
-
- protected AmbientState getAmbientState() {
- return mAmbientState;
- }
-
- public PanelViewController(
- NotificationPanelView view,
- FalsingManager falsingManager,
- DozeLog dozeLog,
- KeyguardStateController keyguardStateController,
- SysuiStatusBarStateController statusBarStateController,
- NotificationShadeWindowController notificationShadeWindowController,
- VibratorHelper vibratorHelper,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- LatencyTracker latencyTracker,
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- LockscreenGestureLogger lockscreenGestureLogger,
- ShadeExpansionStateManager shadeExpansionStateManager,
- AmbientState ambientState,
- InteractionJankMonitor interactionJankMonitor,
- ShadeLogger shadeLogger,
- SystemClock systemClock) {
- keyguardStateController.addCallback(new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- updateExpandedHeightToMaxHeight();
- }
- });
- mAmbientState = ambientState;
- mView = view;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mLockscreenGestureLogger = lockscreenGestureLogger;
- mShadeExpansionStateManager = shadeExpansionStateManager;
- mShadeLog = shadeLogger;
- TouchHandler touchHandler = createTouchHandler();
- mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mViewName = mResources.getResourceName(mView.getId());
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
-
- mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(touchHandler);
- mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
-
- mResources = mView.getResources();
- mKeyguardStateController = keyguardStateController;
- mStatusBarStateController = statusBarStateController;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mFlingAnimationUtils = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(0.5f)
- .setSpeedUpFactor(0.6f)
- .setX2(0.6f)
- .setY2(0.84f)
- .build();
- mLatencyTracker = latencyTracker;
- mBounceInterpolator = new BounceInterpolator();
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
- mNotificationsDragEnabled = mResources.getBoolean(
- R.bool.config_enableNotificationShadeDrag);
- mVibratorHelper = vibratorHelper;
- mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mSystemClock = systemClock;
- }
-
- protected void loadDimens() {
- final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
- mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
- }
-
- protected float getTouchSlop(MotionEvent event) {
- // Adjust the touch slop if another gesture may be being performed.
- return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
- ? mTouchSlop * mSlopMultiplier
- : mTouchSlop;
- }
-
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
- public void setTouchAndAnimationDisabled(boolean disabled) {
- mTouchDisabled = disabled;
- if (mTouchDisabled) {
- cancelHeightAnimator();
- if (mTracking) {
- onTrackingStopped(true /* expanded */);
- }
- notifyExpandingFinished();
- }
- }
-
- public void startExpandLatencyTracking() {
- if (mLatencyTracker.isEnabled()) {
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
- mExpandLatencyTracking = true;
- }
- }
-
- private void startOpening(MotionEvent event) {
- updatePanelExpansionAndVisibility();
- // Reset at start so haptic can be triggered as soon as panel starts to open.
- mHasVibratedOnOpen = false;
- //TODO: keyguard opens QS a different way; log that too?
-
- // Log the position of the swipe that opened the panel
- float width = mCentralSurfaces.getDisplayWidth();
- float height = mCentralSurfaces.getDisplayHeight();
- int rot = mCentralSurfaces.getRotation();
-
- mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
- (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
- mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
- }
-
- /**
- * Maybe vibrate as panel is opened.
- *
- * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
- * being opened programmatically (such as by the open panel gesture), we always play haptic.
- */
- protected void maybeVibrateOnOpening(boolean openingWithTouch) {
- if (mVibrateOnOpening) {
- if (!openingWithTouch || !mHasVibratedOnOpen) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- mHasVibratedOnOpen = true;
- }
- }
- }
-
- protected abstract float getOpeningHeight();
-
- /**
- * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
- * horizontal direction
- */
- private boolean isDirectionUpwards(float x, float y) {
- float xDiff = x - mInitialExpandX;
- float yDiff = y - mInitialExpandY;
- if (yDiff >= 0) {
- return false;
- }
- return Math.abs(yDiff) >= Math.abs(xDiff);
- }
-
- public void startExpandMotion(float newX, float newY, boolean startTracking,
- float expandedHeight) {
- if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- mInitialOffsetOnTouch = expandedHeight;
- mInitialExpandY = newY;
- mInitialExpandX = newX;
- mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
- if (startTracking) {
- mTouchSlopExceeded = true;
- setExpandedHeight(mInitialOffsetOnTouch);
- onTrackingStarted();
- }
- }
-
- private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
- mTrackingPointer = -1;
- mAmbientState.setSwipingUp(false);
- if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
- || Math.abs(y - mInitialExpandY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- mVelocityTracker.computeCurrentVelocity(1000);
- float vel = mVelocityTracker.getYVelocity();
- float vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- final boolean onKeyguard = mKeyguardStateController.isShowing();
- final boolean expand;
- if (mKeyguardStateController.isKeyguardFadingAway()
- || (mInitialTouchFromKeyguard && !onKeyguard)) {
- // Don't expand for any touches that started from the keyguard and ended after the
- // keyguard is gone.
- expand = false;
- } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- if (onKeyguard) {
- expand = true;
- } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
- expand = false;
- } else {
- // If we get a cancel, put the shade back to the state it was in when the
- // gesture started
- expand = !mPanelClosedOnDown;
- }
- } else {
- expand = flingExpands(vel, vectorVel, x, y);
- }
-
- mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
- mCentralSurfaces.isWakeUpComingFromTouch());
- // Log collapse gesture if on lock screen.
- if (!expand && onKeyguard) {
- float displayDensity = mCentralSurfaces.getDisplayDensity();
- int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
- int velocityDp = (int) Math.abs(vel / displayDensity);
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
- }
- @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
- : y - mInitialExpandY > 0 ? QUICK_SETTINGS
- : (mKeyguardStateController.canDismissLockScreen()
- ? UNLOCK : BOUNCER_UNLOCK);
-
- fling(vel, expand, isFalseTouch(x, y, interactionType));
- onTrackingStopped(expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
- && !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick();
- onTrackingStopped(expands);
- }
- mVelocityTracker.clear();
- }
-
- protected float getCurrentExpandVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getYVelocity();
- }
-
- protected abstract int getFalsingThreshold();
-
- protected abstract boolean shouldGestureWaitForTouchSlop();
-
- protected void onTrackingStopped(boolean expand) {
- mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
- updatePanelExpansionAndVisibility();
- }
-
- protected void onTrackingStarted() {
- endClosing();
- mTracking = true;
- mCentralSurfaces.onTrackingStarted();
- notifyExpandingStarted();
- updatePanelExpansionAndVisibility();
- }
-
- /**
- * @return Whether a pair of coordinates are inside the visible view content bounds.
- */
- protected abstract boolean isInContentBounds(float x, float y);
-
- protected void cancelHeightAnimator() {
- if (mHeightAnimator != null) {
- if (mHeightAnimator.isRunning()) {
- mPanelUpdateWhenAnimatorEnds = false;
- }
- mHeightAnimator.cancel();
- }
- endClosing();
- }
-
- private void endClosing() {
- if (mClosing) {
- setIsClosing(false);
- onClosingFinished();
- }
- }
-
- protected abstract boolean canCollapsePanelOnTouch();
-
- protected float getContentHeight() {
- return mExpandedHeight;
- }
-
- /**
- * @param vel the current vertical velocity of the motion
- * @param vectorVel the length of the vectorial velocity
- * @return whether a fling should expands the panel; contracts otherwise
- */
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- if (mFalsingManager.isUnlockingDisabled()) {
- return true;
- }
-
- @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0
- ? QUICK_SETTINGS : (
- mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
-
- if (isFalseTouch(x, y, interactionType)) {
- return true;
- }
- if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return shouldExpandWhenNotFlinging();
- } else {
- return vel > 0;
- }
- }
-
- protected boolean shouldExpandWhenNotFlinging() {
- return getExpandedFraction() > 0.5f;
- }
-
- /**
- * @param x the final x-coordinate when the finger was lifted
- * @param y the final y-coordinate when the finger was lifted
- * @return whether this motion should be regarded as a false touch
- */
- private boolean isFalseTouch(float x, float y,
- @Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(interactionType);
- }
- if (!mTouchAboveFalsingThreshold) {
- return true;
- }
- if (mUpwardsWhenThresholdReached) {
- return false;
- }
- return !isDirectionUpwards(x, y);
- }
-
- protected void fling(float vel, boolean expand) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
- }
-
- protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
- }
-
- protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
- boolean expandBecauseOfFalsing) {
- float target = expand ? getMaxPanelHeight() : 0;
- if (!expand) {
- setIsClosing(true);
- }
- flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
- }
-
- protected void flingToHeight(float vel, boolean expand, float target,
- float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- if (target == mExpandedHeight && mOverExpansion == 0.0f) {
- // We're at the target and didn't fling and there's no overshoot
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsFlinging = true;
- // we want to perform an overshoot animation when flinging open
- final boolean addOverscroll =
- expand
- && !mInSplitShade // Split shade has its own overscroll logic
- && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
- && mOverExpansion == 0.0f
- && vel >= 0;
- final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
- float overshootAmount = 0.0f;
- if (addOverscroll) {
- // Let's overshoot depending on the amount of velocity
- overshootAmount = MathUtils.lerp(
- 0.2f,
- 1.0f,
- MathUtils.saturate(vel
- / (mFlingAnimationUtils.getHighVelocityPxPerSecond()
- * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
- overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
- }
- ValueAnimator animator = createHeightAnimator(target, overshootAmount);
- if (expand) {
- if (expandBecauseOfFalsing && vel < 0) {
- vel = 0;
- }
- mFlingAnimationUtils.apply(animator, mExpandedHeight,
- target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight());
- if (vel == 0) {
- animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
- }
- } else {
- if (shouldUseDismissingAnimation()) {
- if (vel == 0) {
- animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
- animator.setDuration(duration);
- } else {
- mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
- mView.getHeight());
- }
- } else {
- mFlingAnimationUtilsClosing.apply(
- animator, mExpandedHeight, target, vel, mView.getHeight());
- }
-
- // Make it shorter if we run a canned animation
- if (vel == 0) {
- animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
- }
- if (mFixedDuration != NO_FIXED_DURATION) {
- animator.setDuration(mFixedDuration);
- }
- }
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationStart(Animator animation) {
- if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (shouldSpringBack && !mCancelled) {
- // After the shade is flinged open to an overscrolled state, spring back
- // the shade by reducing section padding to 0.
- springBack();
- } else {
- onFlingEnd(mCancelled);
- }
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- private void springBack() {
- if (mOverExpansion == 0) {
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsSpringBackAnimation = true;
- ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
- animator.addUpdateListener(
- animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */));
- animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsSpringBackAnimation = false;
- onFlingEnd(mCancelled);
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- protected void onFlingEnd(boolean cancelled) {
- mIsFlinging = false;
- // No overshoot when the animation ends
- setOverExpansionInternal(0, false /* isFromGesture */);
- setAnimator(null);
- mKeyguardStateController.notifyPanelFlingEnd();
- if (!cancelled) {
- endJankMonitoring();
- notifyExpandingFinished();
- } else {
- cancelJankMonitoring();
- }
- updatePanelExpansionAndVisibility();
- }
-
- protected abstract boolean shouldUseDismissingAnimation();
-
- public String getName() {
- return mViewName;
- }
-
- public void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
- setExpandedHeightInternal(height);
- }
-
- void updateExpandedHeightToMaxHeight() {
- float currentMaxPanelHeight = getMaxPanelHeight();
-
- if (isFullyCollapsed()) {
- return;
- }
-
- if (currentMaxPanelHeight == mExpandedHeight) {
- return;
- }
-
- if (mTracking && !isTrackingBlocked()) {
- return;
- }
-
- if (mHeightAnimator != null && !mIsSpringBackAnimation) {
- mPanelUpdateWhenAnimatorEnds = true;
- return;
- }
-
- setExpandedHeight(currentMaxPanelHeight);
- }
-
- /**
- * Returns drag down distance after which panel should be fully expanded. Usually it's the
- * same as max panel height but for large screen devices (especially split shade) we might
- * want to return different value to shorten drag distance
- */
- public abstract int getMaxPanelTransitionDistance();
-
- public void setExpandedHeightInternal(float h) {
- if (isNaN(h)) {
- Log.wtf(TAG, "ExpandedHeight set to NaN");
- }
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
- if (mExpandLatencyTracking && h != 0f) {
- DejankUtils.postAfterTraversal(
- () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
- mExpandLatencyTracking = false;
- }
- float maxPanelHeight = getMaxPanelTransitionDistance();
- if (mHeightAnimator == null) {
- // Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
- float overExpansionPixels = Math.max(0, h - maxPanelHeight);
- setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
- }
- }
- mExpandedHeight = Math.min(h, maxPanelHeight);
- // If we are closing the panel and we are almost there due to a slow decelerating
- // interpolator, abort the animation.
- if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
- mExpandedHeight = 0f;
- if (mHeightAnimator != null) {
- mHeightAnimator.end();
- }
- }
- mExpansionDragDownAmountPx = h;
- mExpandedFraction = Math.min(1f,
- maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
- mAmbientState.setExpansionFraction(mExpandedFraction);
- onHeightUpdated(mExpandedHeight);
- updatePanelExpansionAndVisibility();
- });
- }
-
- /**
- * @return true if the panel tracking should be temporarily blocked; this is used when a
- * conflicting gesture (opening QS) is happening
- */
- protected abstract boolean isTrackingBlocked();
-
- protected void setOverExpansion(float overExpansion) {
- mOverExpansion = overExpansion;
- }
-
- /**
- * Set the current overexpansion
- *
- * @param overExpansion the amount of overexpansion to apply
- * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
- */
- private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
- if (!isFromGesture) {
- mLastGesturedOverExpansion = -1;
- setOverExpansion(overExpansion);
- } else if (mLastGesturedOverExpansion != overExpansion) {
- mLastGesturedOverExpansion = overExpansion;
- final float heightForFullOvershoot = mView.getHeight() / 3.0f;
- float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
- newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
- setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
- }
- }
-
- protected abstract void onHeightUpdated(float expandedHeight);
-
- /**
- * This returns the maximum height of the panel. Children should override this if their
- * desired height is not the full height.
- *
- * @return the default implementation simply returns the maximum height.
- */
- protected abstract int getMaxPanelHeight();
-
- public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelTransitionDistance() * frac);
- }
-
- public float getExpandedHeight() {
- return mExpandedHeight;
- }
-
- public float getExpandedFraction() {
- return mExpandedFraction;
- }
-
- public boolean isFullyExpanded() {
- return mExpandedHeight >= getMaxPanelHeight();
- }
-
- public boolean isFullyCollapsed() {
- return mExpandedFraction <= 0.0f;
- }
-
- public boolean isCollapsing() {
- return mClosing || mIsLaunchAnimationRunning;
- }
-
- public boolean isFlinging() {
- return mIsFlinging;
- }
-
- public boolean isTracking() {
- return mTracking;
- }
-
- public void collapse(boolean delayed, float speedUpFactor) {
- if (DEBUG) logf("collapse: " + this);
- if (canPanelBeCollapsed()) {
- cancelHeightAnimator();
- notifyExpandingStarted();
-
- // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- setIsClosing(true);
- if (delayed) {
- mNextCollapseSpeedUpFactor = speedUpFactor;
- mView.postDelayed(mFlingCollapseRunnable, 120);
- } else {
- fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
- }
- }
- }
-
- public boolean canPanelBeCollapsed() {
- return !isFullyCollapsed() && !mTracking && !mClosing;
- }
-
- private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
- mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
-
- public void expand(final boolean animate) {
- if (!isFullyCollapsed() && !isCollapsing()) {
- return;
- }
-
- mInstantExpanding = true;
- mAnimateAfterExpanding = animate;
- mUpdateFlingOnLayout = false;
- abortAnimations();
- if (mTracking) {
- onTrackingStopped(true /* expands */); // The panel is expanded after this call.
- }
- if (mExpanding) {
- notifyExpandingFinished();
- }
- updatePanelExpansionAndVisibility();
-
- // Wait for window manager to pickup the change, so we know the maximum height of the panel
- // then.
- mView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (!mInstantExpanding) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- return;
- }
- if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- if (mAnimateAfterExpanding) {
- notifyExpandingStarted();
- beginJankMonitoring();
- fling(0, true /* expand */);
- } else {
- setExpandedFraction(1f);
- }
- mInstantExpanding = false;
- }
- }
- });
-
- // Make sure a layout really happens.
- mView.requestLayout();
- }
-
- public void instantCollapse() {
- abortAnimations();
- setExpandedFraction(0f);
- if (mExpanding) {
- notifyExpandingFinished();
- }
- if (mInstantExpanding) {
- mInstantExpanding = false;
- updatePanelExpansionAndVisibility();
- }
- }
-
- private void abortAnimations() {
- cancelHeightAnimator();
- mView.removeCallbacks(mFlingCollapseRunnable);
- }
-
- protected abstract void onClosingFinished();
-
- protected void startUnlockHintAnimation() {
-
- // We don't need to hint the user if an animation is already running or the user is changing
- // the expansion.
- if (mHeightAnimator != null || mTracking) {
- return;
- }
- notifyExpandingStarted();
- startUnlockHintAnimationPhase1(() -> {
- notifyExpandingFinished();
- onUnlockHintFinished();
- mHintAnimationRunning = false;
- });
- onUnlockHintStarted();
- mHintAnimationRunning = true;
- }
-
- protected void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
- }
-
- protected void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
- }
-
- public boolean isUnlockHintRunning() {
- return mHintAnimationRunning;
- }
-
- /**
- * Phase 1: Move everything upwards.
- */
- private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
- float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
- ValueAnimator animator = createHeightAnimator(target);
- animator.setDuration(250);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- setAnimator(null);
- onAnimationFinished.run();
- } else {
- startUnlockHintAnimationPhase2(onAnimationFinished);
- }
- }
- });
- animator.start();
- setAnimator(animator);
-
- final List<ViewPropertyAnimator> indicationAnimators =
- mKeyguardBottomArea.getIndicationAreaAnimators();
- for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
- indicationAreaAnimator
- .translationY(-mHintDistance)
- .setDuration(250)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(() -> indicationAreaAnimator
- .translationY(0)
- .setDuration(450)
- .setInterpolator(mBounceInterpolator)
- .start())
- .start();
- }
- }
-
- private void setAnimator(ValueAnimator animator) {
- mHeightAnimator = animator;
- if (animator == null && mPanelUpdateWhenAnimatorEnds) {
- mPanelUpdateWhenAnimatorEnds = false;
- updateExpandedHeightToMaxHeight();
- }
- }
-
- /**
- * Phase 2: Bounce down.
- */
- private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
- ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
- animator.setDuration(450);
- animator.setInterpolator(mBounceInterpolator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setAnimator(null);
- onAnimationFinished.run();
- updatePanelExpansionAndVisibility();
- }
- });
- animator.start();
- setAnimator(animator);
- }
-
- private ValueAnimator createHeightAnimator(float targetHeight) {
- return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
- }
-
- /**
- * Create an animator that can also overshoot
- *
- * @param targetHeight the target height
- * @param overshootAmount the amount of overshoot desired
- */
- private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
- float startExpansion = mOverExpansion;
- ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
- animator.addUpdateListener(
- animation -> {
- if (overshootAmount > 0.0f
- // Also remove the overExpansion when collapsing
- || (targetHeight == 0.0f && startExpansion != 0)) {
- final float expansion = MathUtils.lerp(
- startExpansion,
- mPanelFlingOvershootAmount * overshootAmount,
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
- animator.getAnimatedFraction()));
- setOverExpansionInternal(expansion, false /* isFromGesture */);
- }
- setExpandedHeightInternal((float) animation.getAnimatedValue());
- });
- return animator;
- }
-
- /** Update the visibility of {@link NotificationPanelView} if necessary. */
- public void updateVisibility() {
- mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
- }
-
- /** Returns true if {@link NotificationPanelView} should be visible. */
- abstract protected boolean shouldPanelBeVisible();
-
- /**
- * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
- *
- * TODO(b/200063118): Could public calls to this method be replaced with calls to
- * {@link #updateVisibility()}? That would allow us to make this method private.
- */
- public void updatePanelExpansionAndVisibility() {
- mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
- updateVisibility();
- }
-
- public boolean isExpanded() {
- return mExpandedFraction > 0f
- || mInstantExpanding
- || isPanelVisibleBecauseOfHeadsUp()
- || mTracking
- || mHeightAnimator != null
- && !mIsSpringBackAnimation;
- }
-
- protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
-
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- protected boolean onEmptySpaceClick() {
- if (mHintAnimationRunning) {
- return true;
- }
- return onMiddleClicked();
- }
-
- protected abstract boolean onMiddleClicked();
-
- protected abstract boolean isDozing();
-
- public void dump(PrintWriter pw, String[] args) {
- pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
- + " tracking=%s timeAnim=%s%s "
- + "touchDisabled=%s" + "]",
- this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
- mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
- ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
- mTouchDisabled ? "T" : "f"));
- }
-
- public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- public void setIsLaunchAnimationRunning(boolean running) {
- mIsLaunchAnimationRunning = running;
- }
-
- protected void setIsClosing(boolean isClosing) {
- mClosing = isClosing;
- }
-
- protected boolean isClosing() {
- return mClosing;
- }
-
- public void collapseWithDuration(int animationDuration) {
- mFixedDuration = animationDuration;
- collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- mFixedDuration = NO_FIXED_DURATION;
- }
-
- public ViewGroup getView() {
- // TODO: remove this method, or at least reduce references to it.
- return mView;
- }
-
- protected abstract OnLayoutChangeListener createLayoutChangeListener();
-
- protected abstract TouchHandler createTouchHandler();
-
- protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
- return new OnConfigurationChangedListener();
- }
-
- public class TouchHandler implements View.OnTouchListener {
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
- && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
- return false;
- }
-
- /*
- * If the user drags anywhere inside the panel we intercept it if the movement is
- * upwards. This allows closing the shade from anywhere inside the panel.
- *
- * We only do this if the current content is scrolled to the bottom,
- * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
- * gesture
- * possible.
- */
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
- boolean canCollapsePanel = canCollapsePanelOnTouch();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mCentralSurfaces.userActivity();
- mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
- mMinExpandHeight = 0.0f;
- mDownTime = mSystemClock.uptimeMillis();
- if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
- cancelHeightAnimator();
- mTouchSlopExceeded = true;
- return true;
- }
- mInitialExpandY = y;
- mInitialExpandX = x;
- mTouchStartedInEmptyArea = !isInContentBounds(x, y);
- mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
- mMotionAborted = false;
- mPanelClosedOnDown = isFullyCollapsed();
- mCollapsedAndHeadsUpOnDown = false;
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mTouchAboveFalsingThreshold = false;
- addMovement(event);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialExpandX = event.getX(newIndex);
- mInitialExpandY = event.getY(newIndex);
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- mVelocityTracker.clear();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialExpandY;
- addMovement(event);
- final boolean openShadeWithoutHun =
- mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
- || openShadeWithoutHun) {
- float hAbs = Math.abs(h);
- float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop
- || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
- && hAbs > Math.abs(x - mInitialExpandX)) {
- cancelHeightAnimator();
- startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mVelocityTracker.clear();
- break;
- }
- return false;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mInstantExpanding) {
- mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
- return false;
- }
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
- mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
- return false;
- }
- if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
- mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
- return false;
- }
-
- // If dragging should not expand the notifications shade, then return false.
- if (!mNotificationsDragEnabled) {
- if (mTracking) {
- // Turn off tracking if it's on or the shade can get stuck in the down position.
- onTrackingStopped(true /* expand */);
- }
- mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
- return false;
- }
-
- // On expanding, single mouse click expands the panel instead of dragging.
- if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- expand(true);
- }
- return true;
- }
-
- /*
- * We capture touch events here and update the expand height here in case according to
- * the users fingers. This also handles multi-touch.
- *
- * Flinging is also enabled in order to open or close the shade.
- */
-
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = true;
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- mMinExpandHeight = 0.0f;
- mPanelClosedOnDown = isFullyCollapsed();
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mMotionAborted = false;
- mDownTime = mSystemClock.uptimeMillis();
- mTouchAboveFalsingThreshold = false;
- mCollapsedAndHeadsUpOnDown =
- isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
- addMovement(event);
- boolean regularHeightAnimationRunning = mHeightAnimator != null
- && !mHintAnimationRunning && !mIsSpringBackAnimation;
- if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
- mTouchSlopExceeded = regularHeightAnimationRunning
- || mTouchSlopExceededBeforeDown;
- cancelHeightAnimator();
- onTrackingStarted();
- }
- if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
- && !mCentralSurfaces.isBouncerShowing()) {
- startOpening(event);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
- mHandlingPointerUp = true;
- startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
- mHandlingPointerUp = false;
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- endMotionEvent(event, x, y, true /* forceCancel */);
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- addMovement(event);
- if (!isFullyCollapsed()) {
- maybeVibrateOnOpening(true /* openingWithTouch */);
- }
- float h = y - mInitialExpandY;
-
- // If the panel was collapsed when touching, we only need to check for the
- // y-component of the gesture, as we have no conflicting horizontal gesture.
- if (Math.abs(h) > getTouchSlop(event)
- && (Math.abs(h) > Math.abs(x - mInitialExpandX)
- || mIgnoreXTouchSlop)) {
- mTouchSlopExceeded = true;
- if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
- if (mInitialOffsetOnTouch != 0f) {
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- h = 0;
- }
- cancelHeightAnimator();
- onTrackingStarted();
- }
- }
- float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
- newHeight = Math.max(newHeight, mMinExpandHeight);
- if (-h >= getFalsingThreshold()) {
- mTouchAboveFalsingThreshold = true;
- mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
- }
- if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
- // Count h==0 as part of swipe-up,
- // otherwise {@link NotificationStackScrollLayout}
- // wrongly enables stack height updates at the start of lockscreen swipe-up
- mAmbientState.setSwipingUp(h <= 0);
- setExpandedHeightInternal(newHeight);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- addMovement(event);
- endMotionEvent(event, x, y, false /* forceCancel */);
- // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
- if (mHeightAnimator == null) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring();
- } else {
- cancelJankMonitoring();
- }
- }
- break;
- }
- return !mGestureWaitForTouchSlop || mTracking;
- }
- }
-
- protected abstract class OnLayoutChangeListener implements View.OnLayoutChangeListener {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- updateExpandedHeightToMaxHeight();
- mHasLayoutedSinceDown = true;
- if (mUpdateFlingOnLayout) {
- abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
- mUpdateFlingOnLayout = false;
- }
- }
- }
-
- public class OnConfigurationChangedListener implements
- NotificationPanelView.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadDimens();
- }
- }
-
- private void beginJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- mView)
- .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
- mInteractionJankMonitor.begin(builder);
- }
-
- private void endJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().end(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- private void cancelJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().cancel(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- protected float getExpansionFraction() {
- return mExpandedFraction;
- }
-
- protected ShadeExpansionStateManager getPanelExpansionStateManager() {
- return mShadeExpansionStateManager;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 6abf339685e4..ff26766fba66 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -32,10 +32,10 @@ import javax.inject.Inject;
* Dispatches shortcut to System UI components
*/
@SysUISingleton
-public class ShortcutKeyDispatcher extends CoreStartable
- implements ShortcutKeyServiceProxy.Callbacks {
+public class ShortcutKeyDispatcher implements CoreStartable, ShortcutKeyServiceProxy.Callbacks {
private static final String TAG = "ShortcutKeyDispatcher";
+ private final Context mContext;
private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -50,7 +50,7 @@ public class ShortcutKeyDispatcher extends CoreStartable
@Inject
public ShortcutKeyDispatcher(Context context) {
- super(context);
+ mContext = context;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c290ce260cc6..184dc253bfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -189,7 +189,6 @@ public class NotificationLockscreenUserManagerImpl implements
protected NotificationPresenter mPresenter;
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
- private boolean mHideSilentNotificationsOnLockscreen;
@Inject
public NotificationLockscreenUserManagerImpl(Context context,
@@ -266,12 +265,6 @@ public class NotificationLockscreenUserManagerImpl implements
UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
- true,
- mLockscreenSettingsObserver,
- UserHandle.USER_ALL);
-
- mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
@@ -340,9 +333,6 @@ public class NotificationLockscreenUserManagerImpl implements
final boolean allowedByDpm = (dpmFlags
& DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
- mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0;
-
setShowLockscreenNotifications(show && allowedByDpm);
if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
index 78077386179a..59afb18195dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -36,8 +36,7 @@ public abstract class QsFrameTranslateController {
/**
* Calculate and translate the QS Frame on the Y-axis.
*/
- public abstract void translateQsFrame(View qsFrame, QS qs, float overExpansion,
- float qsTranslationForFullShadeTransition);
+ public abstract void translateQsFrame(View qsFrame, QS qs, int bottomInset);
/**
* Calculate the top padding for notifications panel. This could be the supplied
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
index 33e224579bef..85b522cbd9d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -27,6 +27,8 @@ import javax.inject.Inject;
/**
* Default implementation of QS Translation. This by default does not do much.
+ * This class can be subclassed to allow System UI variants the flexibility to change position of
+ * the Quick Settings frame.
*/
@SysUISingleton
public class QsFrameTranslateImpl extends QsFrameTranslateController {
@@ -37,8 +39,8 @@ public class QsFrameTranslateImpl extends QsFrameTranslateController {
}
@Override
- public void translateQsFrame(View qsFrame, QS qs, float overExpansion,
- float qsTranslationForFullShadeTransition) {
+ public void translateQsFrame(View qsFrame, QS qs, int bottomInset) {
+ // Empty implementation by default, meant to be overridden by subclasses.
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 59022c0ffbf2..822840dfd86b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -66,11 +66,12 @@ import javax.inject.Inject;
* splitted screen.
*/
@SysUISingleton
-public class InstantAppNotifier extends CoreStartable
- implements CommandQueue.Callbacks, KeyguardStateController.Callback {
+public class InstantAppNotifier
+ implements CoreStartable, CommandQueue.Callbacks, KeyguardStateController.Callback {
private static final String TAG = "InstantAppNotifier";
public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5;
+ private final Context mContext;
private final Handler mHandler = new Handler();
private final Executor mUiBgExecutor;
private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
@@ -83,7 +84,7 @@ public class InstantAppNotifier extends CoreStartable
CommandQueue commandQueue,
@UiBackground Executor uiBgExecutor,
KeyguardStateController keyguardStateController) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mUiBgExecutor = uiBgExecutor;
mKeyguardStateController = keyguardStateController;
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 ccf6feca6992..8f3eb4f7e223 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
@@ -440,6 +440,42 @@ class HeadsUpCoordinator @Inject constructor(
override fun onEntryCleanUp(entry: NotificationEntry) {
mHeadsUpViewBinder.abortBindCallback(entry)
}
+
+ /**
+ * Identify notifications whose heads-up state changes when the notification rankings are
+ * updated, and have those changed notifications alert if necessary.
+ *
+ * This method will occur after any operations in onEntryAdded or onEntryUpdated, so any
+ * handling of ranking changes needs to take into account that we may have just made a
+ * PostedEntry for some of these notifications.
+ */
+ override fun onRankingApplied() {
+ // Because a ranking update may cause some notifications that are no longer (or were
+ // 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
+ // 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.
+ if (entry.hasInterrupted()) continue
+
+ // The cases where we should consider this notification to be updated:
+ // - if this entry is not present in PostedEntries, and is now in a shouldHeadsUp
+ // state
+ // - if it is present in PostedEntries and the previous state of shouldHeadsUp
+ // differs from the updated one
+ val shouldHeadsUpEver = mNotificationInterruptStateProvider.checkHeadsUp(entry,
+ /* log= */ false)
+ val postedShouldHeadsUpEver = mPostedEntries[entry.key]?.shouldHeadsUpEver ?: false
+ val shouldUpdateEntry = postedShouldHeadsUpEver != shouldHeadsUpEver
+
+ if (shouldUpdateEntry) {
+ mLogger.logEntryUpdatedByRanking(entry.key, shouldHeadsUpEver)
+ onEntryUpdated(entry)
+ }
+ }
+ }
}
/**
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 204a494c32e8..8625cdbc89d5 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
@@ -59,4 +59,13 @@ class HeadsUpCoordinatorLogger constructor(
" numPostedEntries=$int1 logicalGroupSize=$int2"
})
}
+
+ fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ bool1 = shouldHun
+ }, {
+ "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 6f41425b506d..9a7610ddd354 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -114,7 +114,18 @@ public class HeadsUpViewBinder {
*/
public void unbindHeadsUpView(NotificationEntry entry) {
abortBindCallback(entry);
- mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
+
+ // params may be null if the notification was already removed from the collection but we let
+ // it stick around during a launch animation. In this case, the heads up view has already
+ // been unbound, so we don't need to unbind it.
+ // TODO(b/253081345): Change this back to getStageParams and remove null check.
+ RowContentBindParams params = mStage.tryGetStageParams(entry);
+ if (params == null) {
+ mLogger.entryBindStageParamsNullOnUnbind(entry);
+ return;
+ }
+
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
mLogger.entryContentViewMarkedFreeable(entry);
mStage.requestRebind(entry, e -> mLogger.entryUnbound(e));
}
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 d1feaa05c653..5dbec8dcba20 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
@@ -47,6 +47,14 @@ class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val bu
"start unbinding heads up entry $str1 "
})
}
+
+ fun entryBindStageParamsNullOnUnbind(entry: NotificationEntry) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ }, {
+ "heads up entry bind stage params null on unbind $str1 "
+ })
+ }
}
private const val TAG = "HeadsUpViewBinder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index c956a2ea1836..659df24aad2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -72,7 +72,6 @@ private interface KeyguardNotificationVisibilityProviderImplModule {
@SysUISingleton
private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
- context: Context,
@Main private val handler: Handler,
private val keyguardStateController: KeyguardStateController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -82,7 +81,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val secureSettings: SecureSettings,
private val globalSettings: GlobalSettings
-) : CoreStartable(context), KeyguardNotificationVisibilityProvider {
+) : CoreStartable, KeyguardNotificationVisibilityProvider {
private val showSilentNotifsUri =
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index 7c41800d880d..d626c18e46f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -21,6 +21,7 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -64,7 +65,7 @@ public abstract class BindStage<Params> extends BindRequester {
* Get the stage parameters for the entry. Clients should use this to modify how the stage
* handles the notification content.
*/
- public final Params getStageParams(@NonNull NotificationEntry entry) {
+ public final @NonNull Params getStageParams(@NonNull NotificationEntry entry) {
Params params = mContentParams.get(entry);
if (params == null) {
// TODO: This should throw an exception but there are some cases of re-entrant calls
@@ -79,6 +80,17 @@ public abstract class BindStage<Params> extends BindRequester {
return params;
}
+ // TODO(b/253081345): Remove this method.
+ /**
+ * Get the stage parameters for the entry, or null if there are no stage parameters for the
+ * entry.
+ *
+ * @see #getStageParams(NotificationEntry)
+ */
+ public final @Nullable Params tryGetStageParams(@NonNull NotificationEntry entry) {
+ return mContentParams.get(entry);
+ }
+
/**
* Create a params entry for the notification for this stage.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 087dc71f6cf2..1b006485c83d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1487,7 +1487,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.setAlpha(alpha);
}
if (mChildrenContainer != null) {
- mChildrenContainer.setAlpha(alpha);
+ mChildrenContainer.setContentAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 7b23a56af836..0dda2632db66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -461,6 +461,20 @@ public class NotificationChildrenContainer extends ViewGroup
return mAttachedChildren;
}
+ /**
+ * Sets the alpha on the content, while leaving the background of the container itself as is.
+ *
+ * @param alpha alpha value to apply to the content
+ */
+ public void setContentAlpha(float alpha) {
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ mNotificationHeader.getChildAt(i).setAlpha(alpha);
+ }
+ for (ExpandableNotificationRow child : getAttachedChildren()) {
+ child.setContentAlpha(alpha);
+ }
+ }
+
/** To be called any time the rows have been updated */
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
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 836cacc185c6..55c577f1ea39 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
@@ -1115,6 +1115,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
updateAlgorithmLayoutMinHeight();
updateOwnTranslationZ();
+ // Give The Algorithm information regarding the QS height so it can layout notifications
+ // properly. Needed for some devices that grows notifications down-to-top
+ mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight());
+
// Once the layout has finished, we don't need to animate any scrolling clampings anymore.
mAnimateStackYForContentHeightChange = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8d28f7524f9a..0502159f46cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -417,12 +417,19 @@ public class StackScrollAlgorithm {
}
/**
+ * Update the position of QS Frame.
+ */
+ public void updateQSFrameTop(int qsHeight) {
+ // Intentionally empty for sub-classes in other device form factors to override
+ }
+
+ /**
* Determine the positions for the views. This is the main part of the algorithm.
*
* @param algorithmState The state in which the current pass of the algorithm is currently in
* @param ambientState The current ambient state
*/
- private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
+ protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
if (!ambientState.isOnKeyguard()
|| (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
@@ -448,7 +455,7 @@ public class StackScrollAlgorithm {
* @return Fraction to apply to view height and gap between views.
* Does not include shelf height even if shelf is showing.
*/
- private float getExpansionFractionWithoutShelf(
+ protected float getExpansionFractionWithoutShelf(
StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
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 b6e658fd4eab..b81b3eaba134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -268,8 +268,7 @@ import dagger.Lazy;
* </b>
*/
@SysUISingleton
-public class CentralSurfacesImpl extends CoreStartable implements
- CentralSurfaces {
+public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private static final String BANNER_ACTION_CANCEL =
"com.android.systemui.statusbar.banner_action_cancel";
@@ -290,6 +289,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
+ private final Context mContext;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
@@ -747,7 +747,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager) {
- super(context);
+ mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
mLightBarController = lightBarController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index e3e85728ff83..5e26cf062b58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -47,7 +47,7 @@ class KeyguardLiftController @Inject constructor(
private val asyncSensorManager: AsyncSensorManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dumpManager: DumpManager
-) : Dumpable, CoreStartable(context) {
+) : Dumpable, CoreStartable {
private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
private var isListening = false
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 5b2d69564585..2f0ebf752a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -35,8 +35,8 @@ protected constructor(
protected val controller: UserSwitcherController,
) : BaseAdapter() {
- protected open val users: ArrayList<UserRecord>
- get() = controller.users
+ protected open val users: List<UserRecord>
+ get() = controller.users.filter { !controller.isKeyguardShowing || !it.isRestricted }
init {
controller.addAdapter(WeakReference(this))
@@ -112,6 +112,7 @@ protected constructor(
item.isGuest,
item.isAddSupervisedUser,
isTablet,
+ item.isManageUsers,
)
return checkNotNull(context.getDrawable(iconRes))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 494a4bbbdf9f..c1506541229d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -53,6 +53,7 @@ import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
@@ -456,7 +457,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS
}
void refreshUserOrder() {
- ArrayList<UserRecord> users = super.getUsers();
+ List<UserRecord> users = super.getUsers();
mUsersOrdered = new ArrayList<>(users.size());
for (int i = 0; i < users.size(); i++) {
UserRecord record = users.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
index af39eeed26b0..935fc7f10198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -249,7 +249,7 @@ constructor(
override fun startActivity(intent: Intent) {
if (useInteractor) {
- activityStarter.startActivity(intent, /* dismissShade= */ false)
+ activityStarter.startActivity(intent, /* dismissShade= */ true)
} else {
_oldImpl.startActivity(intent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index 46d2f3ac9ce4..c294c370a601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -399,6 +400,16 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
records.add(userRecord);
}
+ if (canManageUsers()) {
+ records.add(LegacyUserDataHelper.createRecord(
+ mContext,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ /* isRestricted= */ false,
+ /* isSwitchToEnabled= */ true
+ ));
+ }
+
mUiExecutor.execute(() -> {
if (records != null) {
mUsers = records;
@@ -438,6 +449,14 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
}
+ @VisibleForTesting
+ boolean canManageUsers() {
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ return mUserSwitcherEnabled
+ && ((currentUser != null && currentUser.isAdmin())
+ || mAddUsersFromLockScreen.getValue());
+ }
+
private boolean createIsRestricted() {
return !mAddUsersFromLockScreen.getValue();
}
@@ -525,6 +544,8 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
showAddUserDialog(dialogShower);
} else if (record.isAddSupervisedUser) {
startSupervisedUserActivity();
+ } else if (record.isManageUsers) {
+ startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
} else {
onUserListItemClicked(record.info.id, record, dialogShower);
}
@@ -984,7 +1005,7 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
@Override
public void startActivity(Intent intent) {
- mActivityStarter.startActivity(intent, true);
+ mActivityStarter.startActivity(intent, /* dismissShade= */ true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 09298b60ff51..b1b8341d9584 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -37,19 +37,20 @@ import dagger.Lazy;
* Serves as a collection of UI components, rather than showing its own UI.
*/
@SysUISingleton
-public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks {
+public class TvStatusBar implements CoreStartable, CommandQueue.Callbacks {
private static final String ACTION_SHOW_PIP_MENU =
"com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+ private final Context mContext;
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
@Inject
public TvStatusBar(Context context, CommandQueue commandQueue,
Lazy<AssistManager> assistManagerLazy) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mAssistManagerLazy = assistManagerLazy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
index c1997446c126..b938c9002d90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
@@ -35,9 +35,9 @@ import javax.inject.Inject
*/
@SysUISingleton
class VpnStatusObserver @Inject constructor(
- context: Context,
+ private val context: Context,
private val securityController: SecurityController
-) : CoreStartable(context),
+) : CoreStartable,
SecurityController.SecurityControllerCallback {
private var vpnConnected = false
@@ -102,7 +102,7 @@ class VpnStatusObserver @Inject constructor(
.apply {
vpnName?.let {
setContentText(
- mContext.getString(
+ context.getString(
R.string.notification_disclosure_vpn_text, it
)
)
@@ -111,23 +111,23 @@ class VpnStatusObserver @Inject constructor(
.build()
private fun createVpnConnectedNotificationBuilder() =
- Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN)
+ Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
.setSmallIcon(vpnIconId)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_SYSTEM)
.extend(Notification.TvExtender())
.setOngoing(true)
- .setContentTitle(mContext.getString(R.string.notification_vpn_connected))
- .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext))
+ .setContentTitle(context.getString(R.string.notification_vpn_connected))
+ .setContentIntent(VpnConfig.getIntentForStatusPanel(context))
private fun createVpnDisconnectedNotification() =
- Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN)
+ Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
.setSmallIcon(vpnIconId)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_SYSTEM)
.extend(Notification.TvExtender())
.setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS)
- .setContentTitle(mContext.getString(R.string.notification_vpn_disconnected))
+ .setContentTitle(context.getString(R.string.notification_vpn_disconnected))
.build()
companion object {
@@ -137,4 +137,4 @@ class VpnStatusObserver @Inject constructor(
private const val TAG = "TvVpnNotification"
private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
index 8026ba517820..b92725bd7cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
@@ -18,13 +18,13 @@ package com.android.systemui.statusbar.tv.notifications;
import android.annotation.Nullable;
import android.app.Notification;
-import android.content.Context;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.SparseArray;
import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.NotificationListener;
import javax.inject.Inject;
@@ -32,7 +32,8 @@ import javax.inject.Inject;
/**
* Keeps track of the notifications on TV.
*/
-public class TvNotificationHandler extends CoreStartable implements
+@SysUISingleton
+public class TvNotificationHandler implements CoreStartable,
NotificationListener.NotificationHandler {
private static final String TAG = "TvNotificationHandler";
private final NotificationListener mNotificationListener;
@@ -41,8 +42,7 @@ public class TvNotificationHandler extends CoreStartable implements
private Listener mUpdateListener;
@Inject
- public TvNotificationHandler(Context context, NotificationListener notificationListener) {
- super(context);
+ public TvNotificationHandler(NotificationListener notificationListener) {
mNotificationListener = notificationListener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
index 892fedcc8ce2..dbbd0b8613de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
@@ -35,14 +35,15 @@ import javax.inject.Inject;
* Offers control methods for the notification panel handler on TV devices.
*/
@SysUISingleton
-public class TvNotificationPanel extends CoreStartable implements CommandQueue.Callbacks {
+public class TvNotificationPanel implements CoreStartable, CommandQueue.Callbacks {
private static final String TAG = "TvNotificationPanel";
+ private final Context mContext;
private final CommandQueue mCommandQueue;
private final String mNotificationHandlerPackage;
@Inject
public TvNotificationPanel(Context context, CommandQueue commandQueue) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mNotificationHandlerPackage = mContext.getResources().getString(
com.android.internal.R.string.config_notificationHandlerPackage);
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 4450b76a878c..5cbdf7c43a12 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -62,7 +62,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
@LayoutRes private val viewLayoutRes: Int,
private val windowTitle: String,
private val wakeReason: String,
-) : CoreStartable(context) {
+) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index a345d99b47c6..3d56f2317660 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -100,7 +100,7 @@ import javax.inject.Inject;
* associated work profiles
*/
@SysUISingleton
-public class ThemeOverlayController extends CoreStartable implements Dumpable {
+public class ThemeOverlayController implements CoreStartable, Dumpable {
protected static final String TAG = "ThemeOverlayController";
private static final boolean DEBUG = true;
@@ -114,6 +114,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable {
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final Context mContext;
private final boolean mIsMonetEnabled;
private final UserTracker mUserTracker;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -361,8 +362,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable {
UserManager userManager, DeviceProvisionedController deviceProvisionedController,
UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
@Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
- super(context);
-
+ mContext = context;
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 9eb34a42a0a9..ed14c8ad150c 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -50,13 +50,14 @@ import javax.inject.Inject;
* Controls display of text toasts.
*/
@SysUISingleton
-public class ToastUI extends CoreStartable implements CommandQueue.Callbacks {
+public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
// values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
private static final String TAG = "ToastUI";
+ private final Context mContext;
private final CommandQueue mCommandQueue;
private final INotificationManager mNotificationManager;
private final IAccessibilityManager mIAccessibilityManager;
@@ -90,7 +91,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks {
@Nullable IAccessibilityManager accessibilityManager,
ToastFactory toastFactory, ToastLogger toastLogger
) {
- super(context);
+ mContext = context;
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
mIAccessibilityManager = accessibilityManager;
@@ -179,7 +180,7 @@ public class ToastUI extends CoreStartable implements CommandQueue.Callbacks {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
if (newConfig.orientation != mOrientation) {
mOrientation = newConfig.orientation;
if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 3ce5ca3d0301..10a09dd169e8 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -203,9 +203,9 @@ public abstract class TvSystemUIModule {
@Provides
@SysUISingleton
- static TvNotificationHandler provideTvNotificationHandler(Context context,
+ static TvNotificationHandler provideTvNotificationHandler(
NotificationListener notificationListener) {
- return new TvNotificationHandler(context, notificationListener);
+ return new TvNotificationHandler(notificationListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index fc20ac241e38..6ed3a093c8e3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -26,8 +26,6 @@ import android.os.Trace
import android.view.Choreographer
import android.view.Display
import android.view.DisplayInfo
-import android.view.IRotationWatcher
-import android.view.IWindowManager
import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
@@ -40,6 +38,7 @@ import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -58,7 +57,7 @@ constructor(
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
@UiBackground private val backgroundExecutor: Executor,
- private val windowManagerInterface: IWindowManager
+ private val rotationChangeProvider: RotationChangeProvider,
) {
private val transitionListener = TransitionListener()
@@ -78,7 +77,7 @@ constructor(
fun init() {
deviceStateManager.registerCallback(executor, FoldListener())
unfoldTransitionProgressProvider.addCallback(transitionListener)
- windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
+ rotationChangeProvider.addCallback(rotationWatcher)
val containerBuilder =
SurfaceControl.Builder(SurfaceSession())
@@ -86,7 +85,9 @@ constructor(
.setName("unfold-overlay-container")
displayAreaHelper.get().attachToRootDisplayArea(
- Display.DEFAULT_DISPLAY, containerBuilder) { builder ->
+ Display.DEFAULT_DISPLAY,
+ containerBuilder
+ ) { builder ->
executor.execute {
overlayContainer = builder.build()
@@ -244,8 +245,8 @@ constructor(
}
}
- private inner class RotationWatcher : IRotationWatcher.Stub() {
- override fun onRotationChanged(newRotation: Int) =
+ private inner class RotationWatcher : RotationChangeProvider.RotationListener {
+ override fun onRotationChanged(newRotation: Int) {
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
if (currentRotation != newRotation) {
currentRotation = newRotation
@@ -253,6 +254,7 @@ constructor(
root?.relayout(getLayoutParams())
}
}
+ }
}
private inner class FoldListener :
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index eea6ac0e72e9..59ad24a3e7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -17,11 +17,11 @@
package com.android.systemui.unfold
import android.content.Context
-import android.view.IWindowManager
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -65,11 +65,11 @@ class UnfoldTransitionModule {
@Singleton
fun provideNaturalRotationProgressProvider(
context: Context,
- windowManager: IWindowManager,
+ rotationChangeProvider: RotationChangeProvider,
unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
): Optional<NaturalRotationUnfoldProgressProvider> =
unfoldTransitionProgressProvider.map { provider ->
- NaturalRotationUnfoldProgressProvider(context, windowManager, provider)
+ NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, provider)
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 4dc78f9ec8a6..bf706735d531 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -21,7 +21,6 @@ import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -56,11 +55,12 @@ import javax.inject.Inject;
/** */
@SysUISingleton
-public class StorageNotification extends CoreStartable {
+public class StorageNotification implements CoreStartable {
private static final String TAG = "StorageNotification";
private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
+ private final Context mContext;
// TODO: delay some notifications to avoid bumpy fast operations
@@ -69,7 +69,7 @@ public class StorageNotification extends CoreStartable {
@Inject
public StorageNotification(Context context) {
- super(context);
+ mContext = context;
}
private static class MoveInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 108ab43977e9..7f1195b78c77 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -16,426 +16,41 @@
package com.android.systemui.user
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.LayerDrawable
import android.os.Bundle
-import android.os.UserManager
-import android.provider.Settings
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.MotionEvent
import android.view.View
-import android.view.ViewGroup
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
-import androidx.constraintlayout.helper.widget.Flow
import androidx.lifecycle.ViewModelProvider
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.util.UserIcons
-import com.android.settingslib.Utils
-import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
import javax.inject.Inject
-import kotlin.math.ceil
-
-private const val USER_VIEW = "user_view"
/** Support a fullscreen user switcher */
open class UserSwitcherActivity
@Inject
constructor(
- private val userSwitcherController: UserSwitcherController,
- private val broadcastDispatcher: BroadcastDispatcher,
private val falsingCollector: FalsingCollector,
- private val falsingManager: FalsingManager,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
- private val flags: FeatureFlags,
private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
) : ComponentActivity() {
- private lateinit var parent: UserSwitcherRootView
- private lateinit var broadcastReceiver: BroadcastReceiver
- private var popupMenu: UserSwitcherPopupMenu? = null
- private lateinit var addButton: View
- private var addUserRecords = mutableListOf<UserRecord>()
- private val onBackCallback = OnBackInvokedCallback { finish() }
- private val userSwitchedCallback: UserTracker.Callback =
- object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- finish()
- }
- }
- // When the add users options become available, insert another option to manage users
- private val manageUserRecord =
- UserRecord(
- null /* info */,
- null /* picture */,
- false /* isGuest */,
- false /* isCurrent */,
- false /* isAddUser */,
- false /* isRestricted */,
- false /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
-
- private val adapter: UserAdapter by lazy { UserAdapter() }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- createActivity()
- }
-
- @VisibleForTesting
- fun createActivity() {
setContentView(R.layout.user_switcher_fullscreen)
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
- if (isUsingModernArchitecture()) {
- Log.d(TAG, "Using modern architecture.")
- val viewModel =
- ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
- UserSwitcherViewBinder.bind(
- view = requireViewById(R.id.user_switcher_root),
- viewModel = viewModel,
- lifecycleOwner = this,
- layoutInflater = layoutInflater,
- falsingCollector = falsingCollector,
- onFinish = this::finish,
- )
- return
- } else {
- Log.d(TAG, "Not using modern architecture.")
- }
-
- parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
-
- parent.touchHandler =
- object : Gefingerpoken {
- override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.onTouchEvent(ev)
- return false
- }
- }
-
- requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } }
-
- addButton =
- requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } }
-
- onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackCallback
+ val viewModel =
+ ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+ UserSwitcherViewBinder.bind(
+ view = requireViewById(R.id.user_switcher_root),
+ viewModel = viewModel,
+ lifecycleOwner = this,
+ layoutInflater = layoutInflater,
+ falsingCollector = falsingCollector,
+ onFinish = this::finish,
)
-
- userSwitcherController.init(parent)
- initBroadcastReceiver()
-
- parent.post { buildUserViews() }
- userTracker.addCallback(userSwitchedCallback, mainExecutor)
- }
-
- private fun showPopupMenu() {
- val items = mutableListOf<UserRecord>()
- addUserRecords.forEach { items.add(it) }
-
- var popupMenuAdapter =
- ItemAdapter(
- this,
- R.layout.user_switcher_fullscreen_popup_item,
- layoutInflater,
- { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
- { item: UserRecord ->
- adapter.findUserIcon(item, true).mutate().apply {
- setTint(
- resources.getColor(
- R.color.user_switcher_fullscreen_popup_item_tint,
- getTheme()
- )
- )
- }
- }
- )
- popupMenuAdapter.addAll(items)
-
- popupMenu =
- UserSwitcherPopupMenu(this).apply {
- setAnchorView(addButton)
- setAdapter(popupMenuAdapter)
- setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long ->
- if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
- return@setOnItemClickListener
- }
- // -1 for the header
- val item = popupMenuAdapter.getItem(pos - 1)
- if (item == manageUserRecord) {
- val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
- this@UserSwitcherActivity.startActivity(i)
- } else {
- adapter.onUserListItemClicked(item)
- }
-
- dismiss()
- popupMenu = null
-
- if (!item.isAddUser) {
- this@UserSwitcherActivity.finish()
- }
- }
-
- show()
- }
- }
-
- private fun buildUserViews() {
- var count = 0
- var start = 0
- for (i in 0 until parent.getChildCount()) {
- if (parent.getChildAt(i).getTag() == USER_VIEW) {
- if (count == 0) start = i
- count++
- }
- }
- parent.removeViews(start, count)
- addUserRecords.clear()
- val flow = requireViewById<Flow>(R.id.flow)
- val totalWidth = parent.width
- val userViewCount = adapter.getTotalUserViews()
- val maxColumns = getMaxColumns(userViewCount)
- val horizontalGap =
- resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
- val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
- val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
-
- flow.setMaxElementsWrap(maxColumns)
-
- for (i in 0 until adapter.getCount()) {
- val item = adapter.getItem(i)
- if (adapter.doNotRenderUserView(item)) {
- addUserRecords.add(item)
- } else {
- val userView = adapter.getView(i, null, parent)
- userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply {
- val lp = layoutParams
- if (maxWidgetDiameter < lp.width) {
- lp.width = maxWidgetDiameter
- lp.height = maxWidgetDiameter
- layoutParams = lp
- }
- }
-
- userView.setId(View.generateViewId())
- parent.addView(userView)
-
- // Views must have an id and a parent in order for Flow to lay them out
- flow.addView(userView)
-
- userView.setOnClickListener { v ->
- if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) {
- return@setOnClickListener
- }
-
- if (!item.isCurrent || item.isGuest) {
- adapter.onUserListItemClicked(item)
- }
- }
- }
- }
-
- if (!addUserRecords.isEmpty()) {
- addUserRecords.add(manageUserRecord)
- addButton.visibility = View.VISIBLE
- } else {
- addButton.visibility = View.GONE
- }
- }
-
- override fun onBackPressed() {
- if (isUsingModernArchitecture()) {
- return super.onBackPressed()
- }
-
- finish()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- if (isUsingModernArchitecture()) {
- return
- }
- destroyActivity()
- }
-
- @VisibleForTesting
- fun destroyActivity() {
- onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback)
- broadcastDispatcher.unregisterReceiver(broadcastReceiver)
- userTracker.removeCallback(userSwitchedCallback)
- }
-
- private fun initBroadcastReceiver() {
- broadcastReceiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.getAction()
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- finish()
- }
- }
- }
-
- val filter = IntentFilter()
- filter.addAction(Intent.ACTION_SCREEN_OFF)
- broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
- }
-
- @VisibleForTesting
- fun getMaxColumns(userCount: Int): Int {
- return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt()
- }
-
- private fun isUsingModernArchitecture(): Boolean {
- return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
- }
-
- /** Provides views to populate the option menu. */
- private class ItemAdapter(
- val parentContext: Context,
- val resource: Int,
- val layoutInflater: LayoutInflater,
- val textGetter: (UserRecord) -> String,
- val iconGetter: (UserRecord) -> Drawable
- ) : ArrayAdapter<UserRecord>(parentContext, resource) {
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val item = getItem(position)
- val view = convertView ?: layoutInflater.inflate(resource, parent, false)
-
- view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) }
- view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) }
-
- return view
- }
- }
-
- private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) {
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val item = getItem(position)
- var view = convertView as ViewGroup?
- if (view == null) {
- view =
- layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false)
- as ViewGroup
- }
- (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) }
- (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) }
-
- view.setEnabled(item.isSwitchToEnabled)
- UserSwitcherController.setSelectableAlpha(view)
- view.setTag(USER_VIEW)
- return view
- }
-
- override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
- return if (item == manageUserRecord) {
- getString(R.string.manage_users)
- } else {
- super.getName(context, item, isTablet)
- }
- }
-
- fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable {
- if (item == manageUserRecord) {
- return getDrawable(R.drawable.ic_manage_users)
- }
- if (item.info == null) {
- return getIconDrawable(this@UserSwitcherActivity, item, isTablet)
- }
- val userIcon = userManager.getUserIcon(item.info.id)
- if (userIcon != null) {
- return BitmapDrawable(userIcon)
- }
- return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
- }
-
- fun getTotalUserViews(): Int {
- return users.count { item -> !doNotRenderUserView(item) }
- }
-
- fun doNotRenderUserView(item: UserRecord): Boolean {
- return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null
- }
-
- private fun getDrawable(item: UserRecord): Drawable {
- var drawable =
- if (item.isGuest) {
- getDrawable(R.drawable.ic_account_circle)
- } else {
- findUserIcon(item)
- }
- drawable.mutate()
-
- if (!item.isCurrent && !item.isSwitchToEnabled) {
- drawable.setTint(
- resources.getColor(
- R.color.kg_user_switcher_restricted_avatar_icon_color,
- getTheme()
- )
- )
- }
-
- val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable
- if (item == userSwitcherController.currentUserRecord) {
- (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
- val stroke =
- resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
- val color =
- Utils.getColorAttrDefaultColor(
- this@UserSwitcherActivity,
- com.android.internal.R.attr.colorAccentPrimary
- )
-
- setStroke(stroke, color)
- }
- }
-
- ld.setDrawableByLayerId(R.id.user_avatar, drawable)
- return ld
- }
-
- override fun notifyDataSetChanged() {
- super.notifyDataSetChanged()
- buildUserViews()
- }
- }
-
- companion object {
- private const val TAG = "UserSwitcherActivity"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index 9370286d7ee7..d4fb5634bd1d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -47,6 +47,9 @@ data class UserRecord(
* If not disabled, this is `null`.
*/
@JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
+
+ /** Whether this record is to go to the Settings page to manage users. */
+ @JvmField val isManageUsers: Boolean = false
) {
/** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
index 1b4746a99f8f..dc004f3603a0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -82,6 +82,15 @@ object UserActionsUtil {
)
}
+ fun canManageUsers(
+ repository: UserRepository,
+ isUserSwitcherEnabled: Boolean,
+ isAddUsersFromLockScreenEnabled: Boolean,
+ ): Boolean {
+ return isUserSwitcherEnabled &&
+ (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled)
+ }
+
/**
* Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
*/
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 142a328b2bc4..ba5a82a42d94 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
@@ -102,6 +102,7 @@ constructor(
interface UserCallback {
/** Returns `true` if this callback can be cleaned-up. */
fun isEvictable(): Boolean = false
+
/** Notifies that the state of users on the device has changed. */
fun onUserStateChanged()
}
@@ -164,10 +165,11 @@ constructor(
get() =
if (isNewImpl) {
combine(
+ repository.selectedUserInfo,
repository.userInfos,
repository.userSwitcherSettings,
keyguardInteractor.isKeyguardShowing,
- ) { userInfos, settings, isDeviceLocked ->
+ ) { _, userInfos, settings, isDeviceLocked ->
buildList {
val hasGuestUser = userInfos.any { it.isGuest }
if (
@@ -183,35 +185,45 @@ constructor(
add(UserActionModel.ENTER_GUEST_MODE)
}
- if (isDeviceLocked && !settings.isAddUsersFromLockscreen) {
+ if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
// The device is locked and our setting to allow actions that add users
// from the lock-screen is not enabled. The guest action from above is
// always allowed, even when the device is locked, but the various "add
// user" actions below are not. We can finish building the list here.
- return@buildList
- }
- if (
- UserActionsUtil.canCreateUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- )
- ) {
- add(UserActionModel.ADD_USER)
+ val canCreateUsers =
+ UserActionsUtil.canCreateUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+
+ if (canCreateUsers) {
+ add(UserActionModel.ADD_USER)
+ }
+
+ if (
+ UserActionsUtil.canCreateSupervisedUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ supervisedUserPackageName,
+ )
+ ) {
+ add(UserActionModel.ADD_SUPERVISED_USER)
+ }
}
if (
- UserActionsUtil.canCreateSupervisedUser(
- manager,
+ UserActionsUtil.canManageUsers(
repository,
settings.isUserSwitcherEnabled,
settings.isAddUsersFromLockscreen,
- supervisedUserPackageName,
)
) {
- add(UserActionModel.ADD_SUPERVISED_USER)
+ add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
}
}
@@ -264,7 +276,10 @@ constructor(
toRecord(
action = it,
selectedUserId = selectedUserInfo.id,
- isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+ isRestricted =
+ it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+ !settings.isAddUsersFromLockscreen,
)
}
)
@@ -482,12 +497,12 @@ constructor(
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
.setPackage(supervisedUserPackageName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
- /* dismissShade= */ false,
+ /* dismissShade= */ true,
)
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
activityStarter.startActivity(
Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ false,
+ /* dismissShade= */ true,
)
}
} else {
@@ -575,20 +590,13 @@ constructor(
private suspend fun toRecord(
action: UserActionModel,
selectedUserId: Int,
- isAddFromLockscreenEnabled: Boolean,
+ isRestricted: Boolean,
): UserRecord {
return LegacyUserDataHelper.createRecord(
context = applicationContext,
selectedUserId = selectedUserId,
actionType = action,
- isRestricted =
- if (action == UserActionModel.ENTER_GUEST_MODE) {
- // Entering guest mode is never restricted, so it's allowed to happen from the
- // lockscreen even if the "add from lockscreen" system setting is off.
- false
- } else {
- !isAddFromLockscreenEnabled
- },
+ isRestricted = isRestricted,
isSwitchToEnabled =
canSwitchUsers(selectedUserId) &&
// If the user is auto-created is must not be currently resetting.
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
index 137de1544b2d..03a7470a3fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -80,6 +80,7 @@ object LegacyUserDataHelper {
context = context,
selectedUserId = selectedUserId,
),
+ isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
}
@@ -90,6 +91,7 @@ object LegacyUserDataHelper {
record.isAddUser -> UserActionModel.ADD_USER
record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
record.isGuest -> UserActionModel.ENTER_GUEST_MODE
+ record.isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
else -> error("Not a known action: $record")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 15fdc352d864..e74232df3ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -22,7 +22,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.android.systemui.R
import com.android.systemui.user.data.source.UserRecord
-import kotlin.math.ceil
/**
* Defines utility functions for helping with legacy UI code for users.
@@ -33,16 +32,6 @@ import kotlin.math.ceil
*/
object LegacyUserUiHelper {
- /** Returns the maximum number of columns for user items in the user switcher. */
- fun getMaxUserSwitcherItemColumns(userCount: Int): Int {
- // TODO(b/243844097): remove this once we remove the old user switcher implementation.
- return if (userCount < 5) {
- 4
- } else {
- ceil(userCount / 2.0).toInt()
- }
- }
-
@JvmStatic
@DrawableRes
fun getUserSwitcherActionIconResourceId(
@@ -50,6 +39,7 @@ object LegacyUserUiHelper {
isGuest: Boolean,
isAddSupervisedUser: Boolean,
isTablet: Boolean = false,
+ isManageUsers: Boolean,
): Int {
return if (isAddUser && isTablet) {
R.drawable.ic_account_circle_filled
@@ -59,6 +49,8 @@ object LegacyUserUiHelper {
R.drawable.ic_account_circle
} else if (isAddSupervisedUser) {
R.drawable.ic_add_supervised_user
+ } else if (isManageUsers) {
+ R.drawable.ic_manage_users
} else {
R.drawable.ic_avatar_user
}
@@ -85,6 +77,7 @@ object LegacyUserUiHelper {
isAddUser = record.isAddUser,
isAddSupervisedUser = record.isAddSupervisedUser,
isTablet = isTablet,
+ isManageUsers = record.isManageUsers,
)
)
}
@@ -114,8 +107,9 @@ object LegacyUserUiHelper {
isAddUser: Boolean,
isAddSupervisedUser: Boolean,
isTablet: Boolean = false,
+ isManageUsers: Boolean,
): Int {
- check(isGuest || isAddUser || isAddSupervisedUser)
+ check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers)
return when {
isGuest && isGuestUserAutoCreated && isGuestUserResetting ->
@@ -125,6 +119,7 @@ object LegacyUserUiHelper {
isGuest -> com.android.internal.R.string.guest_name
isAddUser -> com.android.settingslib.R.string.user_add_user
isAddSupervisedUser -> R.string.add_user_supervised
+ isManageUsers -> R.string.manage_users
else -> error("This should never happen!")
}
}
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 6e7b5232d818..91c592177d19 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
@@ -48,7 +48,7 @@ constructor(
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val interactor: UserInteractor,
private val featureFlags: FeatureFlags,
-) : CoreStartable(context) {
+) : CoreStartable {
private var currentDialog: Dialog? = null
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 5b83df7b4a36..219dae29117f 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
@@ -19,7 +19,6 @@ package com.android.systemui.user.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -30,6 +29,7 @@ import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import javax.inject.Inject
+import kotlin.math.ceil
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
@@ -52,8 +52,7 @@ private constructor(
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
/** The maximum number of columns that the user selection grid should use. */
- val maximumUserColumns: Flow<Int> =
- users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
+ val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) }
private val _isMenuVisible = MutableStateFlow(false)
/**
@@ -118,6 +117,15 @@ private constructor(
_isMenuVisible.value = false
}
+ /** Returns the maximum number of columns for user items in the user switcher. */
+ private fun getMaxUserSwitcherItemColumns(userCount: Int): Int {
+ return if (userCount < 5) {
+ 4
+ } else {
+ ceil(userCount / 2.0).toInt()
+ }
+ }
+
private fun createFinishRequestedFlow(): Flow<Boolean> {
var mostRecentSelectedUserId: Int? = null
var mostRecentIsInteractive: Boolean? = null
@@ -171,27 +179,23 @@ private constructor(
return UserActionViewModel(
viewKey = model.ordinal.toLong(),
iconResourceId =
- if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
- R.drawable.ic_manage_users
- } else {
- LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
- isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
- isAddUser = model == UserActionModel.ADD_USER,
- isGuest = model == UserActionModel.ENTER_GUEST_MODE,
- )
- },
+ LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
+ isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+ isAddUser = model == UserActionModel.ADD_USER,
+ isGuest = model == UserActionModel.ENTER_GUEST_MODE,
+ isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ isTablet = true,
+ ),
textResourceId =
- if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
- R.string.manage_users
- } else {
- LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
- isGuest = model == UserActionModel.ENTER_GUEST_MODE,
- isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
- isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
- isAddUser = model == UserActionModel.ADD_USER,
- )
- },
+ LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
+ isGuest = model == UserActionModel.ENTER_GUEST_MODE,
+ isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
+ isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+ isAddUser = model == UserActionModel.ADD_USER,
+ isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ isTablet = true,
+ ),
onClicked = {
userInteractor.executeAction(action = model)
// We don't finish because we want to show a dialog over the full-screen UI and
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 53da213eb38e..2efeda932ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -32,7 +32,8 @@ import java.util.Arrays;
import javax.inject.Inject;
// NOT Singleton. Started per-user.
-public class NotificationChannels extends CoreStartable {
+/** */
+public class NotificationChannels implements CoreStartable {
public static String ALERTS = "ALR";
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
// Deprecated. Please use or create a more specific channel that users will better understand
@@ -45,9 +46,11 @@ public class NotificationChannels extends CoreStartable {
public static String INSTANT = "INS";
public static String SETUP = "STP";
+ private final Context mContext;
+
@Inject
public NotificationChannels(Context context) {
- super(context);
+ mContext = context;
}
public static void createAll(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 619e50b47f13..a0a0372426ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -564,12 +564,13 @@ public class GarbageMonitor implements Dumpable {
/** */
@SysUISingleton
- public static class Service extends CoreStartable implements Dumpable {
+ public static class Service implements CoreStartable, Dumpable {
+ private final Context mContext;
private final GarbageMonitor mGarbageMonitor;
@Inject
public Service(Context context, GarbageMonitor garbageMonitor) {
- super(context);
+ mContext = context;
mGarbageMonitor = garbageMonitor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 87fb2a692682..0b3521b048c4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -31,18 +31,19 @@ import java.io.PrintWriter;
import javax.inject.Inject;
@SysUISingleton
-public class VolumeUI extends CoreStartable {
+public class VolumeUI implements CoreStartable {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
private final Handler mHandler = new Handler();
private boolean mEnabled;
+ private final Context mContext;
private VolumeDialogComponent mVolumeComponent;
@Inject
public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
- super(context);
+ mContext = context;
mVolumeComponent = volumeDialogComponent;
}
@@ -59,8 +60,7 @@ public class VolumeUI extends CoreStartable {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
if (!mEnabled) return;
mVolumeComponent.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3472cb1c2a7d..fbc6a582da2e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -89,8 +89,10 @@ import javax.inject.Inject;
* -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
*/
@SysUISingleton
-public final class WMShell extends CoreStartable
- implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
+public final class WMShell implements
+ CoreStartable,
+ CommandQueue.Callbacks,
+ ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = WMShell.class.getName();
private static final int INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_DIALOG_SHOWING
@@ -102,6 +104,7 @@ public final class WMShell extends CoreStartable
| SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+ private final Context mContext;
// Shell interfaces
private final ShellInterface mShell;
private final Optional<Pip> mPipOptional;
@@ -163,7 +166,8 @@ public final class WMShell extends CoreStartable
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
- public WMShell(Context context,
+ public WMShell(
+ Context context,
ShellInterface shell,
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
@@ -179,7 +183,7 @@ public final class WMShell extends CoreStartable
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
@Main Executor sysUiMainExecutor) {
- super(context);
+ mContext = context;
mShell = shell;
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index aa671d1e3790..91b544b8265c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -17,7 +17,6 @@
package com.android.keyguard
import android.hardware.biometrics.BiometricSourceType
-import org.mockito.Mockito.verify
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -30,9 +29,10 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Captor
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -63,7 +63,6 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() {
whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId)
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
- mContext,
uiEventLogger,
keyguardUpdateMonitor,
sessionTracker)
@@ -195,4 +194,4 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() {
verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture())
updateMonitorCallback = updateMonitorCallbackCaptor.value
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index c6ebaa8bb46c..48e82397e826 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -221,15 +221,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
public void onResourcesUpdate_callsThroughOnRotationChange() {
// Rotation is the same, shouldn't cause an update
mKeyguardSecurityContainerController.updateResources();
- verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
// Update rotation. Should trigger update
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
mKeyguardSecurityContainerController.updateResources();
- verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
}
private void touchDown() {
@@ -263,8 +265,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
}
@Test
@@ -275,8 +278,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
}
@Test
@@ -285,8 +289,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
setupGetSecurityView();
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
- verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ }
+
+ @Test
+ public void addUserSwitcherCallback() {
+ ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
+ captor = ArgumentCaptor.forClass(
+ KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
+
+ setupGetSecurityView();
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
+ verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
+ any(UserSwitcherController.class),
+ captor.capture());
+ captor.getValue().showUnlockToContinueMessage();
+ verify(mKeyguardPasswordViewControllerMock).showMessage(
+ getContext().getString(R.string.keyguard_unlock_to_continue), null);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 52f8825c724b..82d3ca785161 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -119,7 +119,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int systemBarInsetAmount = 0;
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, () -> {});
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -141,7 +141,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int systemBarInsetAmount = paddingBottom + 1;
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, () -> {});
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -158,9 +158,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
@Test
public void testDefaultViewMode() {
mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, () -> {
+ });
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, () -> {});
ConstraintSet.Constraint viewFlipperConstraint =
getViewConstraint(mSecurityViewFlipper.getId());
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
@@ -377,7 +378,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
private void setupUserSwitcher() {
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
- mGlobalSettings, mFalsingManager, mUserSwitcherController);
+ mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
}
private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -387,7 +388,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
0 /* flags */);
users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */, null /* enforcedAdmin */));
+ false /* isAddSupervisedUser */, null /* enforcedAdmin */,
+ false /* isManageUsers */));
}
return users;
}
@@ -395,7 +397,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, () -> {});
}
/** Get the ConstraintLayout constraint of the view. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index df10dfe9f160..2319f4386798 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -231,7 +231,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mExecutor.runAllReady();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d52612b000bc..f2ae7a1b2a5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,6 +35,8 @@ import android.view.WindowInsets
import android.view.WindowManager
import android.widget.ScrollView
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
@@ -143,6 +145,35 @@ class AuthContainerViewTest : SysuiTestCase() {
}
@Test
+ fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ waitForIdleSync()
+
+ val requestID = authContainer?.requestId ?: 0L
+
+ // Simulate keyboard was shown on the credential view
+ val windowInsetsController = container.windowInsetsController
+ spyOn(windowInsetsController)
+ spyOn(container.rootWindowInsets)
+ doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
+
+ container.onWindowFocusChanged(false)
+ waitForIdleSync()
+
+ // Expect hiding IME request will be invoked when dismissing the view
+ verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
+ )
+ assertThat(container.parent).isNull()
+ }
+
+ @Test
fun testActionAuthenticated_sendsDismissedAuthenticated() {
val container = initializeFingerprintContainer()
container.mBiometricCallback.onAction(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 91214a85ddd5..e7e6918325a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -38,6 +38,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxyFake;
import org.junit.Before;
@@ -47,6 +49,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import javax.inject.Provider;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -55,11 +60,15 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Mock
private ClipboardManager mClipboardManager;
@Mock
- private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
+ private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
+ @Mock
+ private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
@Mock
private ClipboardOverlayController mOverlayController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private DeviceConfigProxyFake mDeviceConfigProxy;
private ClipData mSampleClipData;
@@ -72,12 +81,17 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<String> mStringCaptor;
+ @Spy
+ private Provider<ClipboardOverlayController> mOverlayControllerProvider;
+
@Before
public void setup() {
+ mOverlayControllerProvider = () -> mOverlayController;
+
MockitoAnnotations.initMocks(this);
- when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
- mOverlayController);
+ when(mClipboardOverlayControllerLegacyFactory.create(any()))
+ .thenReturn(mOverlayControllerLegacy);
when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
@@ -94,7 +108,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"false", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
verifyZeroInteractions(mClipboardManager);
verifyZeroInteractions(mUiEventLogger);
@@ -105,7 +120,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any());
verifyZeroInteractions(mUiEventLogger);
@@ -113,16 +129,58 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Test
public void test_consecutiveCopies() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory).create(any());
+ verify(mClipboardOverlayControllerLegacyFactory).create(any());
- verify(mOverlayController).setClipData(mClipDataCaptor.capture(), mStringCaptor.capture());
+ verify(mOverlayControllerLegacy).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+
+ verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
+
+ // Should clear the overlay controller
+ mRunnableCaptor.getValue().run();
+
+ listener.onPrimaryClipChanged();
+
+ verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+
+ // Not calling the runnable here, just change the clip again and verify that the overlay is
+ // NOT recreated.
+
+ listener.onPrimaryClipChanged();
+
+ verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+ verifyZeroInteractions(mOverlayControllerProvider);
+ }
+
+ @Test
+ public void test_consecutiveCopies_new() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
+ "true", false);
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+ listener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
assertEquals(mSampleClipData, mClipDataCaptor.getValue());
assertEquals(mSampleSource, mStringCaptor.getValue());
@@ -134,14 +192,15 @@ public class ClipboardListenerTest extends SysuiTestCase {
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+ verify(mOverlayControllerProvider, times(2)).get();
// Not calling the runnable here, just change the clip again and verify that the overlay is
// NOT recreated.
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+ verify(mOverlayControllerProvider, times(2)).get();
+ verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
}
@Test
@@ -169,4 +228,40 @@ public class ClipboardListenerTest extends SysuiTestCase {
assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData,
ClipboardListener.SHELL_PACKAGE, false));
}
+
+ @Test
+ public void test_logging_enterAndReenter() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+
+ listener.onPrimaryClipChanged();
+ listener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+ }
+
+ @Test
+ public void test_logging_enterAndReenter_new() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+
+ listener.onPrimaryClipChanged();
+ listener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
new file mode 100644
index 000000000000..d96ca91e36bd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.clipboardoverlay;
+
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+
+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.animation.Animator;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipboardOverlayControllerTest extends SysuiTestCase {
+
+ private ClipboardOverlayController mOverlayController;
+ @Mock
+ private ClipboardOverlayView mClipboardOverlayView;
+ @Mock
+ private ClipboardOverlayWindow mClipboardOverlayWindow;
+ @Mock
+ private BroadcastSender mBroadcastSender;
+ @Mock
+ private TimeoutHandler mTimeoutHandler;
+ @Mock
+ private UiEventLogger mUiEventLogger;
+
+ @Mock
+ private Animator mAnimator;
+
+ private ClipData mSampleClipData;
+
+ @Captor
+ private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
+ private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+
+ mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
+ new ClipData.Item("Test Item"));
+
+ mOverlayController = new ClipboardOverlayController(
+ mContext,
+ mClipboardOverlayView,
+ mClipboardOverlayWindow,
+ getFakeBroadcastDispatcher(),
+ mBroadcastSender,
+ mTimeoutHandler,
+ mUiEventLogger);
+ verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
+ mCallbacks = mOverlayCallbacksCaptor.getValue();
+ }
+
+ @Test
+ public void test_setClipData_nullData() {
+ ClipData clipData = null;
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_invalidImageData() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData() {
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipData(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls() {
+ when(mAnimator.isRunning()).thenReturn(true);
+
+ mOverlayController.setClipData(mSampleClipData, "");
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onShareTapped() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ mCallbacks.onShareButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onDismissTapped() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_multipleDismissals_dismissesOnce() {
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
deleted file mode 100644
index c7c2cd8d7b4b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
+++ /dev/null
@@ -1,93 +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.clipboardoverlay;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.DeviceConfigProxyFake;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ClipboardOverlayEventTest extends SysuiTestCase {
-
- @Mock
- private ClipboardManager mClipboardManager;
- @Mock
- private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
- @Mock
- private ClipboardOverlayController mOverlayController;
- @Mock
- private UiEventLogger mUiEventLogger;
-
- private final String mSampleSource = "Example source";
-
- private ClipboardListener mClipboardListener;
-
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
- mOverlayController);
- when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
- ClipData sampleClipData = new ClipData("Test", new String[]{"text/plain"},
- new ClipData.Item("Test Item"));
- when(mClipboardManager.getPrimaryClip()).thenReturn(sampleClipData);
- when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
-
- DeviceConfigProxyFake deviceConfigProxy = new DeviceConfigProxyFake();
- deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "true", false);
-
- mClipboardListener = new ClipboardListener(getContext(), deviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
- }
-
- @Test
- public void test_enterAndReenter() {
- mClipboardListener.start();
-
- mClipboardListener.onPrimaryClipChanged();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index 571dd3d1faf3..9f4a7c820efc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -71,7 +71,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
- mController = new ComplicationTypesUpdater(mContext, mDreamBackend, mExecutor,
+ mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
mSecureSettings, mDreamOverlayStateController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
index 314a30b2d14a..ec448f94ba83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -82,7 +82,6 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase {
public void testComplicationAdded() {
final DreamClockTimeComplication.Registrant registrant =
new DreamClockTimeComplication.Registrant(
- mContext,
mDreamOverlayStateController,
mComplication);
registrant.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index db6082d52501..aa8c93edce68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -115,7 +115,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
@Test
public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
- new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ new DreamHomeControlsComplication.Registrant(mComplication,
mDreamOverlayStateController, mControlsComponent);
registrant.start();
@@ -128,7 +128,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
@Test
public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
- new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ new DreamHomeControlsComplication.Registrant(mComplication,
mDreamOverlayStateController, mControlsComponent);
registrant.start();
@@ -141,7 +141,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
@Test
public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
- new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ new DreamHomeControlsComplication.Registrant(mComplication,
mDreamOverlayStateController, mControlsComponent);
registrant.start();
@@ -154,7 +154,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
@Test
public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
final DreamHomeControlsComplication.Registrant registrant =
- new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ new DreamHomeControlsComplication.Registrant(mComplication,
mDreamOverlayStateController, mControlsComponent);
registrant.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index fa8f88a08368..c8b2b2556828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -24,7 +24,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -48,8 +47,6 @@ import java.util.Collections;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class SmartSpaceComplicationTest extends SysuiTestCase {
- @Mock
- private Context mContext;
@Mock
private DreamSmartspaceController mSmartspaceController;
@@ -80,7 +77,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
private SmartSpaceComplication.Registrant getRegistrant() {
return new SmartSpaceComplication.Registrant(
- mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b6d7559dbcbb..b4d5464d1177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -12,20 +12,20 @@
* WITHOUT 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.usecase
+package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+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.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
@@ -195,6 +195,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -208,6 +209,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
underTest =
@@ -259,7 +261,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
underTest.onQuickAffordanceClicked(
configKey = homeControls::class,
- animationController = animationController,
+ expandable = expandable,
)
if (startActivity) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 1dd919aba88d..65fd6e576650 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -12,9 +12,10 @@
* WITHOUT 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.usecase
+package com.android.systemui.keyguard.domain.interactor
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -22,13 +23,12 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -103,6 +103,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
+ toggle = KeyguardQuickAffordanceToggleState.On,
)
)
@@ -123,6 +124,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
job.cancel()
}
@@ -152,6 +154,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index 6ea1daa7704f..e99c139e9e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,7 +40,7 @@ abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig
override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
+ expandable: Expandable?,
): OnClickedResult {
return onClickedResult
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index dede4ec0210c..a809f0547ee6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -20,7 +20,7 @@ package com.android.systemui.keyguard.domain.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
@@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations
class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var component: ControlsComponent
- @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
@@ -103,7 +103,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
- val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+ val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
@@ -113,7 +113,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
- val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+ val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 0a4478f27448..98dc4c4f6f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -24,11 +24,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+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.plugins.ActivityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -40,7 +42,6 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -135,8 +136,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun onQuickAffordanceClicked() {
val animationController: ActivityLaunchAnimator.Controller = mock()
+ val expandable: Expandable = mock {
+ whenever(this.activityLaunchController()).thenReturn(animationController)
+ }
- assertThat(underTest.onQuickAffordanceClicked(animationController))
+ assertThat(underTest.onQuickAffordanceClicked(expandable))
.isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
verify(walletController)
.startQuickAccessUiIntent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 96544e7b7da6..d674c89c0e14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -20,7 +20,7 @@ import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -31,6 +31,7 @@ import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePositio
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -59,7 +60,7 @@ import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
- @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -130,6 +131,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
TestConfig(
isVisible = true,
isClickable = true,
+ isActivated = true,
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
@@ -505,6 +507,12 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
}
KeyguardQuickAffordanceConfig.State.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+ toggle =
+ when (testConfig.isActivated) {
+ true -> KeyguardQuickAffordanceToggleState.On
+ false -> KeyguardQuickAffordanceToggleState.Off
+ null -> KeyguardQuickAffordanceToggleState.NotSupported
+ }
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
@@ -521,12 +529,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
+ assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
if (testConfig.isVisible) {
assertThat(viewModel.icon).isEqualTo(testConfig.icon)
viewModel.onClicked.invoke(
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = configKey,
- animationController = animationController,
+ expandable = expandable,
)
)
if (testConfig.intent != null) {
@@ -542,6 +551,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private data class TestConfig(
val isVisible: Boolean,
val isClickable: Boolean = false,
+ val isActivated: Boolean = false,
val icon: Icon? = null,
val canShowWhileLocked: Boolean = false,
val intent: Intent? = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index b8e9cf48f3e2..dc5522efe406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -82,7 +82,6 @@ public class SessionTrackerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mSessionTracker = new SessionTracker(
- mContext,
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 5ad354247a04..f34c2ac57a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -25,6 +25,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
@@ -398,4 +403,24 @@ class MediaCarouselControllerTest : SysuiTestCase() {
// added to the end because it was active less recently.
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
}
+
+ @Test
+ fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
+ val delta = 0.0001F
+ val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
+ val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
+ whenever(mediaHostStatesManager.mediaHostStates)
+ .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
+ whenever(mediaHostState.visible).thenReturn(true)
+ mediaCarouselController.currentEndLocation = LOCATION_QS
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
+
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
new file mode 100644
index 000000000000..622a512720d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.media
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.util.animation.MeasurementInput
+import com.android.systemui.util.animation.TransitionLayout
+import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.animation.WidgetState
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.floatThat
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class MediaViewControllerTest : SysuiTestCase() {
+ private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
+ private val mediaHostStatesManager = MediaHostStatesManager()
+ private val configurationController =
+ com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
+ private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ @Mock lateinit var logger: MediaViewLogger
+ @Mock private lateinit var mockViewState: TransitionViewState
+ @Mock private lateinit var mockCopiedState: TransitionViewState
+ @Mock private lateinit var detailWidgetState: WidgetState
+ @Mock private lateinit var controlWidgetState: WidgetState
+ @Mock private lateinit var mediaTitleWidgetState: WidgetState
+ @Mock private lateinit var mediaContainerWidgetState: WidgetState
+
+ val delta = 0.0001F
+
+ private lateinit var mediaViewController: MediaViewController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mediaViewController =
+ MediaViewController(context, configurationController, mediaHostStatesManager, logger)
+ }
+
+ @Test
+ fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
+ mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ player.measureState = TransitionViewState().apply { this.height = 100 }
+ mediaHostStateHolder.expansion = 1f
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ mediaHostStateHolder.measurementInput =
+ MeasurementInput(widthMeasureSpec, heightMeasureSpec)
+
+ // Test no squish
+ mediaHostStateHolder.squishFraction = 1f
+ assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
+
+ // Test half squish
+ mediaHostStateHolder.squishFraction = 0.5f
+ assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
+ }
+
+ @Test
+ fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() {
+ mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
+ recommendation.measureState = TransitionViewState().apply { this.height = 100 }
+ mediaHostStateHolder.expansion = 1f
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ mediaHostStateHolder.measurementInput =
+ MeasurementInput(widthMeasureSpec, heightMeasureSpec)
+
+ // Test no squish
+ mediaHostStateHolder.squishFraction = 1f
+ assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
+
+ // Test half squish
+ mediaHostStateHolder.squishFraction = 0.5f
+ assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
+ }
+
+ @Test
+ fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(
+ mutableMapOf(
+ R.id.media_progress_bar to controlWidgetState,
+ R.id.header_artist to detailWidgetState
+ )
+ )
+
+ val detailSquishMiddle =
+ TRANSFORM_BEZIER.getInterpolation(
+ (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, detailSquishMiddle)
+ verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+
+ val detailSquishEnd =
+ TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
+ mediaViewController.squishViewState(mockViewState, detailSquishEnd)
+ verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+
+ val controlSquishMiddle =
+ TRANSFORM_BEZIER.getInterpolation(
+ (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, controlSquishMiddle)
+ verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+
+ val controlSquishEnd =
+ TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
+ mediaViewController.squishViewState(mockViewState, controlSquishEnd)
+ verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+ }
+
+ @Test
+ fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(
+ mutableMapOf(
+ R.id.media_title1 to mediaTitleWidgetState,
+ R.id.media_cover1_container to mediaContainerWidgetState
+ )
+ )
+
+ val containerSquishMiddle =
+ TRANSFORM_BEZIER.getInterpolation(
+ (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
+ verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+
+ val containerSquishEnd =
+ TRANSFORM_BEZIER.getInterpolation(
+ (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, containerSquishEnd)
+ verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+
+ val titleSquishMiddle =
+ TRANSFORM_BEZIER.getInterpolation(
+ (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, titleSquishMiddle)
+ verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+
+ val titleSquishEnd =
+ TRANSFORM_BEZIER.getInterpolation(
+ (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION
+ )
+ mediaViewController.squishViewState(mockViewState, titleSquishEnd)
+ verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 2f52950a9ee4..af530163e289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -73,7 +73,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase {
@Test
public void testOnMediaDataLoaded_complicationAddition() {
- final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
sentinel.start();
@@ -94,7 +94,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase {
@Test
public void testOnMediaDataRemoved_complicationRemoval() {
- final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
sentinel.start();
@@ -114,7 +114,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase {
@Test
public void testOnMediaDataLoaded_complicationRemoval() {
- final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
sentinel.start();
@@ -139,7 +139,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase {
public void testOnMediaDataLoaded_mediaComplicationDisabled_doesNotAddComplication() {
when(mFeatureFlags.isEnabled(DREAM_MEDIA_COMPLICATION)).thenReturn(false);
- final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mMediaDataManager,
mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags);
sentinel.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 0e9d2799dddb..6adce7a827b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -80,6 +80,7 @@ import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.DeadZone;
@@ -197,6 +198,8 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private UserContextProvider mUserContextProvider;
@Mock
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
private Resources mResources;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -474,7 +477,8 @@ public class NavigationBarTest extends SysuiTestCase {
mNavigationBarTransitions,
mEdgeBackGestureHandler,
Optional.of(mock(BackAnimation.class)),
- mUserContextProvider));
+ mUserContextProvider,
+ mWakefulnessLifecycle));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4e9b2325b899..c377c374148f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -46,6 +46,7 @@ import com.android.settingslib.fuelgauge.Estimate;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -84,6 +85,7 @@ public class PowerUITest extends SysuiTestCase {
private PowerUI mPowerUI;
@Mock private EnhancedEstimates mEnhancedEstimates;
@Mock private PowerManager mPowerManager;
+ @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private IThermalService mThermalServiceMock;
private IThermalEventListener mUsbThermalEventListener;
private IThermalEventListener mSkinThermalEventListener;
@@ -680,7 +682,7 @@ public class PowerUITest extends SysuiTestCase {
private void createPowerUi() {
mPowerUI = new PowerUI(
mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
- mMockWarnings, mEnhancedEstimates, mPowerManager);
+ mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 2a4996f259dc..760bb9bec559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -192,16 +192,6 @@ class FooterActionsViewModelTest : SysuiTestCase() {
// UserManager change.
assertThat(iconTint()).isNull()
- // Trigger a user info change: there should now be a tint.
- userInfoController.updateInfo { userAccount = "doe" }
- assertThat(iconTint())
- .isEqualTo(
- Utils.getColorAttrDefaultColor(
- context,
- android.R.attr.colorForeground,
- )
- )
-
// Make sure we don't tint the icon if it is a user image (and not the default image), even
// in guest mode.
userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
new file mode 100644
index 000000000000..c6ce51a28dd3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.screenshot;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class DraggableConstraintLayoutTest extends SysuiTestCase {
+
+ @Mock
+ DraggableConstraintLayout.SwipeDismissCallbacks mCallbacks;
+
+ private DraggableConstraintLayout mDraggableConstraintLayout;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDraggableConstraintLayout = new DraggableConstraintLayout(mContext, null, 0);
+ }
+
+ @Test
+ public void test_dismissDoesNotCallSwipeInitiated() {
+ mDraggableConstraintLayout.setCallbacks(mCallbacks);
+
+ mDraggableConstraintLayout.dismiss();
+
+ verify(mCallbacks, never()).onSwipeDismissInitiated(any());
+ }
+
+ @Test
+ public void test_onTouchCallsOnInteraction() {
+ mDraggableConstraintLayout.setCallbacks(mCallbacks);
+
+ mDraggableConstraintLayout.onInterceptTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+
+ verify(mCallbacks).onInteraction();
+ }
+
+ @Test
+ public void test_callbacksNotSet() {
+ // just test that it doesn't throw an NPE
+ mDraggableConstraintLayout.onInterceptTouchEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+ mDraggableConstraintLayout.onInterceptHoverEvent(
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0));
+ mDraggableConstraintLayout.dismiss();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 5cb27a47d384..46a502acba16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -99,13 +99,14 @@ class RequestProcessorTest {
policy.getDefaultDisplayId(),
DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
// Request has topComponent added, but otherwise unchanged.
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
assertThat(processedRequest.topComponent).isEqualTo(component)
}
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 2970807afb36..340bc96f80c2 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
@@ -47,6 +47,8 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
+import java.util.ArrayList
+import java.util.function.Consumer
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -57,10 +59,8 @@ import org.mockito.BDDMockito.given
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.ArrayList
-import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -671,8 +671,64 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
}
+ @Test
+ fun testOnRankingApplied_newEntryShouldAlert() {
+ // GIVEN that mEntry has never interrupted in the past, and now should
+ assertFalse(mEntry.hasInterrupted())
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+
+ // WHEN a ranking applied update occurs
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification is shown
+ finishBind(mEntry)
+ verify(mHeadsUpManager).showNotification(mEntry)
+ }
+
+ @Test
+ fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
+ // GIVEN that mEntry has alerted in the past
+ mEntry.setInterruption()
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+
+ // WHEN a ranking applied update occurs
+ 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())
+ }
+
+ @Test
+ fun testOnRankingApplied_entryUpdatedToHun() {
+ // GIVEN that mEntry is added in a state where it should not HUN
+ setShouldHeadsUp(mEntry, false)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // and it is then updated such that it should now HUN
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+
+ // WHEN a ranking applied update occurs
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification is shown
+ finishBind(mEntry)
+ verify(mHeadsUpManager).showNotification(mEntry)
+ }
+
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
+ whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
+ .thenReturn(should)
}
private fun finishBind(entry: NotificationEntry) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index 3f641df376ed..ca6598726a85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -91,6 +91,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
+ when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
+
mViewBinder.unbindHeadsUpView(mEntry);
verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
verifyNoMoreInteractions(mLogger);
@@ -139,6 +141,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
+ when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
+
mViewBinder.unbindHeadsUpView(mEntry);
verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
@@ -150,4 +154,30 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
}
+
+ @Test
+ public void testLoggingForLateUnbindFlow() {
+ AtomicReference<NotifBindPipeline.BindCallback> callback = new AtomicReference<>();
+ when(mBindStage.requestRebind(any(), any())).then(i -> {
+ callback.set(i.getArgument(1));
+ return new CancellationSignal();
+ });
+
+ mViewBinder.bindHeadsUpView(mEntry, null);
+ verify(mLogger).startBindingHun(eq(mEntry));
+ verifyNoMoreInteractions(mLogger);
+ clearInvocations(mLogger);
+
+ callback.get().onBindFinished(mEntry);
+ verify(mLogger).entryBoundSuccessfully(eq(mEntry));
+ verifyNoMoreInteractions(mLogger);
+ clearInvocations(mLogger);
+
+ when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(null);
+
+ mViewBinder.unbindHeadsUpView(mEntry);
+ verify(mLogger).entryBindStageParamsNullOnUnbind(eq(mEntry));
+ verifyNoMoreInteractions(mLogger);
+ clearInvocations(mLogger);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index ad3bd711c23f..7c99568ee75f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -21,6 +21,10 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -31,6 +35,7 @@ import static org.mockito.Mockito.verify;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Log;
import androidx.test.filters.SmallTest;
@@ -100,6 +105,67 @@ public class RowContentBindStageTest extends SysuiTestCase {
verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags));
}
+ class CountingWtfHandler implements Log.TerribleFailureHandler {
+ private Log.TerribleFailureHandler mOldHandler = null;
+ private int mWtfCount = 0;
+
+ public void register() {
+ mOldHandler = Log.setWtfHandler(this);
+ }
+
+ public void unregister() {
+ Log.setWtfHandler(mOldHandler);
+ mOldHandler = null;
+ }
+
+ @Override
+ public void onTerribleFailure(String tag, Log.TerribleFailure what, boolean system) {
+ mWtfCount++;
+ }
+
+ public int getWtfCount() {
+ return mWtfCount;
+ }
+ }
+
+ @Test
+ public void testGetStageParamsAfterCleanUp() {
+ // GIVEN an entry whose params have already been deleted.
+ RowContentBindParams originalParams = mRowContentBindStage.getStageParams(mEntry);
+ mRowContentBindStage.deleteStageParams(mEntry);
+
+ // WHEN a caller calls getStageParams.
+ CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+ countingWtfHandler.register();
+
+ RowContentBindParams blankParams = mRowContentBindStage.getStageParams(mEntry);
+
+ countingWtfHandler.unregister();
+
+ // THEN getStageParams logs a WTF and returns blank params created to avoid a crash.
+ assertEquals(1, countingWtfHandler.getWtfCount());
+ assertNotNull(blankParams);
+ assertNotSame(originalParams, blankParams);
+ }
+
+ @Test
+ public void testTryGetStageParamsAfterCleanUp() {
+ // GIVEN an entry whose params have already been deleted.
+ mRowContentBindStage.deleteStageParams(mEntry);
+
+ // WHEN a caller calls getStageParams.
+ CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+ countingWtfHandler.register();
+
+ RowContentBindParams nullParams = mRowContentBindStage.tryGetStageParams(mEntry);
+
+ countingWtfHandler.unregister();
+
+ // THEN getStageParams does NOT log a WTF and returns null to indicate missing params.
+ assertEquals(0, countingWtfHandler.getWtfCount());
+ assertNull(nullParams);
+ }
+
@Test
public void testRebindAllContentViews() {
// GIVEN a view with content bound.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
index 76ecc1c7f36d..169f4fb2715b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
@@ -57,14 +57,18 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.telephony.TelephonyListenerManager
import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
+import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -123,7 +127,7 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() {
private val ownerId = UserHandle.USER_SYSTEM
private val ownerInfo = UserInfo(ownerId, "Owner", null,
UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM,
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
UserManager.USER_TYPE_FULL_SYSTEM)
private val guestId = 1234
private val guestInfo = UserInfo(guestId, "Guest", null,
@@ -597,6 +601,76 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() {
}
@Test
+ fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.USER_SWITCHER_ENABLED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(1)
+
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(1)
+ setupController()
+ assertTrue(userSwitcherController.canManageUsers())
+ }
+
+ @Test
+ fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.USER_SWITCHER_ENABLED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(0)
+
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(1)
+ setupController()
+ assertFalse(userSwitcherController.canManageUsers())
+ }
+
+ @Test
+ fun testCanManageUser_userSwitcherEnabled_isAdmin() {
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.USER_SWITCHER_ENABLED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(1)
+
+ setupController()
+ assertTrue(userSwitcherController.canManageUsers())
+ }
+
+ @Test
+ fun testCanManageUser_userSwitcherDisabled_isAdmin() {
+ `when`(
+ globalSettings.getIntForUser(
+ eq(Settings.Global.USER_SWITCHER_ENABLED),
+ anyInt(),
+ eq(UserHandle.USER_SYSTEM)
+ )
+ ).thenReturn(0)
+
+ setupController()
+ assertFalse(userSwitcherController.canManageUsers())
+ }
+
+ @Test
fun addUserSwitchCallback() {
val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
verify(broadcastDispatcher).registerReceiver(
@@ -632,4 +706,22 @@ class UserSwitcherControllerOldImplTest : SysuiTestCase() {
bgExecutor.runAllReady()
verify(userManager).createGuest(context)
}
+
+ @Test
+ fun onUserItemClicked_manageUsers() {
+ val manageUserRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ ownerId,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ isRestricted = false,
+ isSwitchToEnabled = true
+ )
+
+ userSwitcherController.onUserListItemClicked(manageUserRecord, null)
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(),
+ eq(true)
+ )
+ Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 7e0704007700..e18dd3a3c846 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -25,16 +25,21 @@ import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -48,6 +53,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Mock
private lateinit var handler: Handler
+ @Mock
+ private lateinit var rotationChangeProvider: RotationChangeProvider
+
+ @Captor
+ private lateinit var rotationListener: ArgumentCaptor<RotationListener>
+
private val foldProvider = TestFoldProvider()
private val screenOnStatusProvider = TestScreenOnStatusProvider()
private val testHingeAngleProvider = TestHingeAngleProvider()
@@ -76,6 +87,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
screenOnStatusProvider,
foldProvider,
activityTypeProvider,
+ rotationChangeProvider,
context.mainExecutor,
handler
)
@@ -92,6 +104,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
})
foldStateProvider.start()
+ verify(rotationChangeProvider).addCallback(capture(rotationListener))
+
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
@@ -372,6 +386,27 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
assertThat(testHingeAngleProvider.isStarted).isFalse()
}
+ @Test
+ fun onRotationChanged_whileInProgress_cancelled() {
+ setFoldState(folded = false)
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+
+ rotationListener.value.onRotationChanged(1)
+
+ assertThat(foldUpdates).containsExactly(
+ FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
+ }
+
+ @Test
+ fun onRotationChanged_whileNotInProgress_noUpdates() {
+ setFoldState(folded = true)
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
+
+ rotationListener.value.onRotationChanged(1)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
+ }
+
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
new file mode 100644
index 000000000000..85cfef727954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RotationChangeProviderTest : SysuiTestCase() {
+
+ private lateinit var rotationChangeProvider: RotationChangeProvider
+
+ @Mock lateinit var windowManagerInterface: IWindowManager
+ @Mock lateinit var listener: RotationListener
+ @Captor lateinit var rotationWatcher: ArgumentCaptor<IRotationWatcher>
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ rotationChangeProvider =
+ RotationChangeProvider(windowManagerInterface, context, fakeExecutor)
+ rotationChangeProvider.addCallback(listener)
+ fakeExecutor.runAllReady()
+ verify(windowManagerInterface).watchRotation(rotationWatcher.capture(), anyInt())
+ }
+
+ @Test
+ fun onRotationChanged_rotationUpdated_listenerReceivesIt() {
+ sendRotationUpdate(42)
+
+ verify(listener).onRotationChanged(42)
+ }
+
+ @Test
+ fun onRotationChanged_subscribersRemoved_noRotationChangeReceived() {
+ sendRotationUpdate(42)
+ verify(listener).onRotationChanged(42)
+
+ rotationChangeProvider.removeCallback(listener)
+ fakeExecutor.runAllReady()
+ sendRotationUpdate(43)
+
+ verify(windowManagerInterface).removeRotationWatcher(any())
+ verifyNoMoreInteractions(listener)
+ }
+
+ private fun sendRotationUpdate(newRotation: Int) {
+ rotationWatcher.value.onRotationChanged(newRotation)
+ fakeExecutor.runAllReady()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index b2cedbf8d606..a25469bfc09b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -16,18 +16,19 @@
package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner
-import android.view.IRotationWatcher
-import android.view.IWindowManager
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-import com.android.systemui.util.mockito.any
+import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
+import com.android.systemui.util.mockito.capture
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
@@ -38,32 +39,26 @@ import org.mockito.MockitoAnnotations
@SmallTest
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
- @Mock
- lateinit var windowManager: IWindowManager
+ @Mock lateinit var rotationChangeProvider: RotationChangeProvider
private val sourceProvider = TestUnfoldTransitionProvider()
- @Mock
- lateinit var transitionListener: TransitionProgressListener
+ @Mock lateinit var transitionListener: TransitionProgressListener
- lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+ @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
- private val rotationWatcherCaptor =
- ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
+ lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- progressProvider = NaturalRotationUnfoldProgressProvider(
- context,
- windowManager,
- sourceProvider
- )
+ progressProvider =
+ NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
progressProvider.init()
- verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
+ verify(rotationChangeProvider).addCallback(capture(rotationListenerCaptor))
progressProvider.addCallback(transitionListener)
}
@@ -127,6 +122,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
}
private fun onRotationChanged(rotation: Int) {
- rotationWatcherCaptor.value.onRotationChanged(rotation)
+ rotationListenerCaptor.value.onRotationChanged(rotation)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
deleted file mode 100644
index 3968bb798bb7..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ /dev/null
@@ -1,152 +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.user
-
-import android.app.Application
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.view.Window
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class UserSwitcherActivityTest : SysuiTestCase() {
- @Mock
- private lateinit var activity: UserSwitcherActivity
- @Mock
- private lateinit var userSwitcherController: UserSwitcherController
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var layoutInflater: LayoutInflater
- @Mock
- private lateinit var falsingCollector: FalsingCollector
- @Mock
- private lateinit var falsingManager: FalsingManager
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var flags: FeatureFlags
- @Mock
- private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory>
- @Mock
- private lateinit var onBackDispatcher: OnBackInvokedDispatcher
- @Mock
- private lateinit var decorView: View
- @Mock
- private lateinit var window: Window
- @Mock
- private lateinit var userSwitcherRootView: UserSwitcherRootView
- @Captor
- private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback>
- var isFinished = false
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- activity = spy(object : UserSwitcherActivity(
- userSwitcherController,
- broadcastDispatcher,
- falsingCollector,
- falsingManager,
- userManager,
- userTracker,
- flags,
- viewModelFactoryLazy,
- ) {
- override fun getOnBackInvokedDispatcher() = onBackDispatcher
- override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock())
- override fun finish() {
- isFinished = true
- }
- })
- `when`(activity.window).thenReturn(window)
- `when`(window.decorView).thenReturn(decorView)
- `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root))
- .thenReturn(userSwitcherRootView)
- `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java))
- `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java))
- `when`(activity.application).thenReturn(mock(Application::class.java))
- doNothing().`when`(activity).setContentView(anyInt())
- }
-
- @Test
- fun testMaxColumns() {
- assertThat(activity.getMaxColumns(3)).isEqualTo(4)
- assertThat(activity.getMaxColumns(4)).isEqualTo(4)
- assertThat(activity.getMaxColumns(5)).isEqualTo(3)
- assertThat(activity.getMaxColumns(6)).isEqualTo(3)
- assertThat(activity.getMaxColumns(7)).isEqualTo(4)
- assertThat(activity.getMaxColumns(9)).isEqualTo(5)
- }
-
- @Test
- fun onCreate_callbackRegistration() {
- activity.createActivity()
- verify(onBackDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
-
- activity.destroyActivity()
- verify(onBackDispatcher).unregisterOnBackInvokedCallback(any())
- }
-
- @Test
- fun onBackInvokedCallback_finishesActivity() {
- activity.createActivity()
- verify(onBackDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture())
-
- onBackInvokedCallback.value.onBackInvoked()
- assertThat(isFinished).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 37c378c9a530..1540f8552002 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -202,6 +202,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
fun `actions - device unlocked`() =
runBlocking(IMMEDIATE) {
val userInfos = createUserInfos(count = 2, includeGuest = false)
+
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -215,6 +216,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
@@ -276,6 +278,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
@@ -283,7 +286,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
}
@Test
- fun `actions - device locked - only guest action is shown`() =
+ fun `actions - device locked - only guest action and manage user is shown`() =
runBlocking(IMMEDIATE) {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -293,7 +296,13 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
var value: List<UserActionModel>? = null
val job = underTest.actions.onEach { value = it }.launchIn(this)
- assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE))
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+ )
+ )
job.cancel()
}
@@ -330,7 +339,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
assertThat(intentCaptor.value.action)
.isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
@@ -342,7 +351,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
}
@@ -561,6 +570,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
UserActionModel.ENTER_GUEST_MODE,
UserActionModel.ADD_USER,
UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
),
)
}
@@ -705,7 +715,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
name,
/* iconPath= */ "",
/* flags= */ if (isPrimary) {
- UserInfo.FLAG_PRIMARY
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
} else {
0
},
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index a5ec0a454412..5a868a4df354 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -20,10 +20,12 @@ import android.content.ContentResolver
import android.content.Context
import android.hardware.SensorManager
import android.os.Handler
+import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -39,11 +41,11 @@ import javax.inject.Singleton
*
* This component is meant to be used for places that don't use dagger. By providing those
* parameters to the factory, all dagger objects are correctly instantiated. See
- * [createUnfoldTransitionProgressProvider] for an example.
+ * [createUnfoldSharedComponent] for an example.
*/
@Singleton
@Component(modules = [UnfoldSharedModule::class])
-internal interface UnfoldSharedComponent {
+interface UnfoldSharedComponent {
@Component.Factory
interface Factory {
@@ -58,9 +60,11 @@ internal interface UnfoldSharedComponent {
@BindsInstance @UnfoldMain executor: Executor,
@BindsInstance @UnfoldBackground backgroundExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
+ @BindsInstance windowManager: IWindowManager,
@BindsInstance contentResolver: ContentResolver = context.contentResolver
): UnfoldSharedComponent
}
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+ val rotationChangeProvider: RotationChangeProvider
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 402dd8474bc4..a1ed17844e8e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -20,6 +20,7 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.SensorManager
import android.os.Handler
+import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -27,14 +28,15 @@ import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import java.util.concurrent.Executor
/**
- * Factory for [UnfoldTransitionProgressProvider].
+ * Factory for [UnfoldSharedComponent].
*
- * This is needed as Launcher has to create the object manually. If dagger is available, this object
- * is provided in [UnfoldSharedModule].
+ * This wraps the autogenerated factory (for discoverability), and is needed as Launcher has to
+ * create the object manually. If dagger is available, this object is provided in
+ * [UnfoldSharedModule].
*
* This should **never** be called from sysui, as the object is already provided in that process.
*/
-fun createUnfoldTransitionProgressProvider(
+fun createUnfoldSharedComponent(
context: Context,
config: UnfoldTransitionConfig,
screenStatusProvider: ScreenStatusProvider,
@@ -44,8 +46,9 @@ fun createUnfoldTransitionProgressProvider(
mainHandler: Handler,
mainExecutor: Executor,
backgroundExecutor: Executor,
- tracingTagPrefix: String
-): UnfoldTransitionProgressProvider =
+ tracingTagPrefix: String,
+ windowManager: IWindowManager,
+): UnfoldSharedComponent =
DaggerUnfoldSharedComponent.factory()
.create(
context,
@@ -57,9 +60,6 @@ fun createUnfoldTransitionProgressProvider(
mainHandler,
mainExecutor,
backgroundExecutor,
- tracingTagPrefix)
- .unfoldTransitionProvider
- .orElse(null)
- ?: throw IllegalStateException(
- "Trying to create " +
- "UnfoldTransitionProgressProvider when the transition is disabled")
+ tracingTagPrefix,
+ windowManager,
+ )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index d54481c72bfd..7117aafba54a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -26,7 +26,8 @@ import com.android.systemui.unfold.util.CallbackController
*
* onTransitionProgress callback could be called on each frame.
*
- * Use [createUnfoldTransitionProgressProvider] to create instances of this interface
+ * Use [createUnfoldSharedComponent] to create instances of this interface when dagger is not
+ * available.
*/
interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgressListener> {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 19cfc805d17b..07473b30dd58 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -24,6 +24,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -40,22 +41,24 @@ constructor(
private val screenStatusProvider: ScreenStatusProvider,
private val foldProvider: FoldProvider,
private val activityTypeProvider: CurrentActivityTypeProvider,
+ private val rotationChangeProvider: RotationChangeProvider,
@UnfoldMain private val mainExecutor: Executor,
@UnfoldMain private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
- @FoldUpdate
- private var lastFoldUpdate: Int? = null
+ @FoldUpdate private var lastFoldUpdate: Int? = null
- @FloatRange(from = 0.0, to = 180.0)
- private var lastHingeAngle: Float = 0f
+ @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener()
- private val timeoutRunnable = TimeoutRunnable()
+ private val timeoutRunnable = Runnable { cancelAnimation() }
+ private val rotationListener = RotationListener {
+ if (isTransitionInProgress) cancelAnimation()
+ }
/**
* Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
@@ -72,6 +75,7 @@ constructor(
foldProvider.registerCallback(foldStateListener, mainExecutor)
screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener)
+ rotationChangeProvider.addCallback(rotationListener)
}
override fun stop() {
@@ -79,6 +83,7 @@ constructor(
foldProvider.unregisterCallback(foldStateListener)
hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop()
+ rotationChangeProvider.removeCallback(rotationListener)
}
override fun addCallback(listener: FoldUpdatesListener) {
@@ -90,14 +95,15 @@ constructor(
}
override val isFinishedOpening: Boolean
- get() = !isFolded &&
+ get() =
+ !isFolded &&
(lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
- lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
+ lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
private val isTransitionInProgress: Boolean
get() =
lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
- lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+ lastFoldUpdate == FOLD_UPDATE_START_CLOSING
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
@@ -168,7 +174,7 @@ constructor(
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
if (DEBUG) {
- Log.d(TAG, stateToString(update))
+ Log.d(TAG, update.name())
}
outputListeners.forEach { it.onFoldUpdate(update) }
lastFoldUpdate = update
@@ -185,6 +191,8 @@ constructor(
handler.removeCallbacks(timeoutRunnable)
}
+ private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
+
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
override fun onScreenTurnedOn() {
@@ -225,16 +233,10 @@ constructor(
onHingeAngle(angle)
}
}
-
- private inner class TimeoutRunnable : Runnable {
- override fun run() {
- notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
- }
- }
}
-private fun stateToString(@FoldUpdate update: Int): String {
- return when (update) {
+fun @receiver:FoldUpdate Int.name() =
+ when (this) {
FOLD_UPDATE_START_OPENING -> "START_OPENING"
FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
@@ -243,15 +245,12 @@ private fun stateToString(@FoldUpdate update: Int): String {
FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
else -> "UNKNOWN"
}
-}
private const val TAG = "DeviceFoldProvider"
private const val DEBUG = false
/** Threshold after which we consider the device fully unfolded. */
-@VisibleForTesting
-const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
+@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
/** Fold animation on top of apps only when the angle exceeds this threshold. */
-@VisibleForTesting
-const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
+@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
new file mode 100644
index 000000000000..0cf8224d3a3f
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.updates
+
+import android.content.Context
+import android.os.RemoteException
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface.Rotation
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.util.CallbackController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Allows to subscribe to rotation changes.
+ *
+ * This is needed as rotation updates from [IWindowManager] are received in a binder thread, while
+ * most of the times we want them in the main one. Updates are provided for the display associated
+ * to [context].
+ */
+class RotationChangeProvider
+@Inject
+constructor(
+ private val windowManagerInterface: IWindowManager,
+ private val context: Context,
+ @UnfoldMain private val mainExecutor: Executor,
+) : CallbackController<RotationChangeProvider.RotationListener> {
+
+ private val listeners = mutableListOf<RotationListener>()
+
+ private val rotationWatcher = RotationWatcher()
+
+ override fun addCallback(listener: RotationListener) {
+ mainExecutor.execute {
+ if (listeners.isEmpty()) {
+ subscribeToRotation()
+ }
+ listeners += listener
+ }
+ }
+
+ override fun removeCallback(listener: RotationListener) {
+ mainExecutor.execute {
+ listeners -= listener
+ if (listeners.isEmpty()) {
+ unsubscribeToRotation()
+ }
+ }
+ }
+
+ private fun subscribeToRotation() {
+ try {
+ windowManagerInterface.watchRotation(rotationWatcher, context.displayId)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+ }
+
+ private fun unsubscribeToRotation() {
+ try {
+ windowManagerInterface.removeRotationWatcher(rotationWatcher)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+ }
+
+ /** Gets notified of rotation changes. */
+ fun interface RotationListener {
+ /** Called once rotation changes. */
+ fun onRotationChanged(@Rotation newRotation: Int)
+ }
+
+ private inner class RotationWatcher : IRotationWatcher.Stub() {
+ override fun onRotationChanged(rotation: Int) {
+ mainExecutor.execute { listeners.forEach { it.onRotationChanged(rotation) } }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index aec5f5ef9ff9..4cf63b35eb92 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3732,21 +3732,34 @@ public class UserBackupManagerService {
Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName
+ " died: cancel current operations");
- // handleCancel() causes the PerformFullTransportBackupTask to go on to
- // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
- // that the package being backed up doesn't get stuck in restricted mode until the
- // backup time-out elapses.
- for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
- if (MORE_DEBUG) {
- Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
- + Integer.toHexString(token));
+ // Offload operation cancellation off the main thread as the cancellation callbacks
+ // might call out to BackupTransport. Other operations started on the same package
+ // before the cancellation callback has executed will also be cancelled by the callback.
+ Runnable cancellationRunnable = () -> {
+ // handleCancel() causes the PerformFullTransportBackupTask to go on to
+ // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
+ // that the package being backed up doesn't get stuck in restricted mode until the
+ // backup time-out elapses.
+ for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
+ + Integer.toHexString(token));
+ }
+ handleCancel(token, true /* cancelAll */);
}
- handleCancel(token, true /* cancelAll */);
- }
+ };
+ getThreadForAsyncOperation(/* operationName */ "agent-disconnected",
+ cancellationRunnable).start();
+
mAgentConnectLock.notifyAll();
}
}
+ @VisibleForTesting
+ Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
+ return new Thread(operation, operationName);
+ }
+
/**
* An application being installed will need a restore pass, then the {@link PackageManager} will
* need to be told when the restore is finished.
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 80d9d972ed0a..a614b723e11c 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -23,8 +23,10 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
+import static com.android.server.companion.MetricUtils.logCreateAssociation;
import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
+import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
import static com.android.server.companion.RolesUtils.isRoleHolder;
import static com.android.server.companion.Utils.prepareForIpc;
@@ -35,8 +37,10 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
+import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -87,7 +91,7 @@ import java.util.Set;
* required.
*
* If the user's approval is NOT required: an {@link AssociationRequestsProcessor} invokes
- * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback, ResultReceiver)}
* which after calling to {@link CompanionDeviceManagerService} to create an association, notifies
* the requester via
* {@link android.companion.CompanionDeviceManager.Callback#onAssociationCreated(AssociationInfo)}.
@@ -99,7 +103,7 @@ import java.util.Set;
* from the Approval UI in via {@link #mOnRequestConfirmationReceiver} and invokes
* {@link #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, ResultReceiver, MacAddress)}
* which one more time checks that the packages holds all necessary permissions before proceeding to
- * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}.
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback, ResultReceiver)}.
*
* @see #processNewAssociationRequest(AssociationRequest, String, int, IAssociationRequestCallback)
* @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
@@ -132,10 +136,10 @@ class AssociationRequestsProcessor {
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
private final @NonNull PackageManagerInternal mPackageManager;
- private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull AssociationStoreImpl mAssociationStore;
AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStore associationStore) {
+ @NonNull AssociationStoreImpl associationStore) {
mContext = service.getContext();
mService = service;
mPackageManager = service.mPackageManagerInternal;
@@ -174,7 +178,7 @@ class AssociationRequestsProcessor {
&& !willAddRoleHolder(request, packageName, userId)) {
// 2a. Create association right away.
createAssociationAndNotifyApplication(request, packageName, userId,
- /*macAddress*/ null, callback);
+ /* macAddress */ null, callback, /* resultReceiver */ null);
return;
}
@@ -253,34 +257,110 @@ class AssociationRequestsProcessor {
}
// 2. Create association and notify the application.
- final AssociationInfo association = createAssociationAndNotifyApplication(
- request, packageName, userId, macAddress, callback);
-
- // 3. Send the association back the Approval Activity, so that it can report back to the app
- // via Activity.setResult().
- final Bundle data = new Bundle();
- data.putParcelable(EXTRA_ASSOCIATION, association);
- resultReceiver.send(RESULT_CODE_ASSOCIATION_CREATED, data);
+ createAssociationAndNotifyApplication(request, packageName, userId, macAddress, callback,
+ resultReceiver);
}
- private AssociationInfo createAssociationAndNotifyApplication(
+ private void createAssociationAndNotifyApplication(
@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
- @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) {
- final AssociationInfo association;
+ @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
final long callingIdentity = Binder.clearCallingIdentity();
try {
- association = mService.createAssociation(userId, packageName, macAddress,
+ createAssociation(userId, packageName, macAddress,
request.getDisplayName(), request.getDeviceProfile(),
- request.getAssociatedDevice(), request.isSelfManaged());
+ request.getAssociatedDevice(), request.isSelfManaged(),
+ callback, resultReceiver);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
+ }
- try {
- callback.onAssociationCreated(association);
- } catch (RemoteException ignore) { }
+ public void createAssociation(@UserIdInt int userId, @NonNull String packageName,
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+ boolean selfManaged, @Nullable IAssociationRequestCallback callback,
+ @Nullable ResultReceiver resultReceiver) {
+ final int id = mService.getNewAssociationIdForPackage(userId, packageName);
+ final long timestamp = System.currentTimeMillis();
+
+ final AssociationInfo association = new AssociationInfo(id, userId, packageName,
+ macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
+ /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
+
+ if (deviceProfile != null) {
+ // If the "Device Profile" is specified, make the companion application a holder of the
+ // corresponding role.
+ addRoleHolderForAssociation(mService.getContext(), association, success -> {
+ if (success) {
+ addAssociationToStore(association, deviceProfile);
+
+ sendCallbackAndFinish(association, callback, resultReceiver);
+ } else {
+ Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName
+ + " to the list of " + deviceProfile + " holders.");
+
+ sendCallbackAndFinish(null, callback, resultReceiver);
+ }
+ });
+ } else {
+ addAssociationToStore(association, null);
+
+ sendCallbackAndFinish(association, callback, resultReceiver);
+ }
+
+ // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
+ // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
+ // that there are other devices with the same profile, so the role holder won't be removed.
+ }
+
+ private void addAssociationToStore(@NonNull AssociationInfo association,
+ @Nullable String deviceProfile) {
+ Slog.i(TAG, "New CDM association created=" + association);
+
+ mAssociationStore.addAssociation(association);
+
+ mService.updateSpecialAccessPermissionForAssociatedPackage(association);
- return association;
+ logCreateAssociation(deviceProfile);
+ }
+
+ private void sendCallbackAndFinish(@Nullable AssociationInfo association,
+ @Nullable IAssociationRequestCallback callback,
+ @Nullable ResultReceiver resultReceiver) {
+ if (association != null) {
+ // Send the association back via the app's callback
+ if (callback != null) {
+ try {
+ callback.onAssociationCreated(association);
+ } catch (RemoteException ignore) {
+ }
+ }
+
+ // Send the association back to CompanionDeviceActivity, so that it can report
+ // back to the app via Activity.setResult().
+ if (resultReceiver != null) {
+ final Bundle data = new Bundle();
+ data.putParcelable(EXTRA_ASSOCIATION, association);
+ resultReceiver.send(RESULT_CODE_ASSOCIATION_CREATED, data);
+ }
+ } else {
+ // Send the association back via the app's callback
+ if (callback != null) {
+ try {
+ // TODO: update to INTERNAL_ERROR once it's added.
+ callback.onFailure(CompanionDeviceManager.REASON_CANCELED);
+ } catch (RemoteException ignore) {
+ }
+ }
+
+ // Send the association back to CompanionDeviceActivity, so that it can report
+ // back to the app via Activity.setResult().
+ if (resultReceiver != null) {
+ final Bundle data = new Bundle();
+ resultReceiver.send(CompanionDeviceManager.RESULT_INTERNAL_ERROR, data);
+ }
+ }
}
private boolean willAddRoleHolder(@NonNull AssociationRequest request,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index f2cb6028e854..d34fc595ea5e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -28,7 +28,6 @@ import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.MetricUtils.logCreateAssociation;
import static com.android.server.companion.MetricUtils.logRemoveAssociation;
import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PackageUtils.getPackageInfo;
@@ -38,7 +37,6 @@ import static com.android.server.companion.PermissionsUtils.enforceCallerCanMana
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
-import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
import static java.util.Objects.requireNonNull;
@@ -55,7 +53,6 @@ import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.DeviceNotAssociatedException;
@@ -819,7 +816,8 @@ public class CompanionDeviceManagerService extends SystemService {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
- legacyCreateAssociation(userId, macAddress, packageName, null);
+ final MacAddress macAddressObj = MacAddress.fromString(macAddress);
+ createNewAssociation(userId, packageName, macAddressObj, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage) {
@@ -871,45 +869,12 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- /**
- * @deprecated use
- * {@link #createAssociation(int, String, MacAddress, CharSequence, String, AssociatedDevice,
- * boolean)}
- */
- @Deprecated
- void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress,
- @NonNull String packageName, @Nullable String deviceProfile) {
- final MacAddress macAddress = MacAddress.fromString(deviceMacAddress);
- createAssociation(userId, packageName, macAddress, null, deviceProfile, null, false);
- }
-
- AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName,
+ void createNewAssociation(@UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
- @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
- boolean selfManaged) {
- final int id = getNewAssociationIdForPackage(userId, packageName);
- final long timestamp = System.currentTimeMillis();
-
- final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
- /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
- Slog.i(TAG, "New CDM association created=" + association);
- mAssociationStore.addAssociation(association);
-
- // If the "Device Profile" is specified, make the companion application a holder of the
- // corresponding role.
- if (deviceProfile != null) {
- addRoleHolderForAssociation(getContext(), association);
- }
-
- updateSpecialAccessPermissionForAssociatedPackage(association);
- logCreateAssociation(deviceProfile);
-
- // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
- // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
- // that there are other devices with the same profile, so the role holder won't be removed.
-
- return association;
+ @Nullable String deviceProfile, boolean isSelfManaged) {
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged,
+ /* callback */ null, /* resultReceiver */ null);
}
@NonNull
@@ -946,7 +911,7 @@ public class CompanionDeviceManagerService extends SystemService {
return usedIdsForPackage;
}
- private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
synchronized (mPreviouslyUsedIds) {
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
@@ -1170,7 +1135,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
+ void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index d24f9e3df382..6889bcd63355 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -17,6 +17,7 @@
package com.android.server.companion;
import android.companion.AssociationInfo;
+import android.net.MacAddress;
import android.os.Binder;
import android.os.ShellCommand;
@@ -63,7 +64,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
int userId = getNextIntArgRequired();
String packageName = getNextArgRequired();
String address = getNextArgRequired();
- mService.legacyCreateAssociation(userId, address, packageName, null);
+ final MacAddress macAddress = MacAddress.fromString(address);
+ mService.createNewAssociation(userId, packageName, macAddress,
+ null, null, false);
}
break;
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 0fff3f488562..f674a7d20b82 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -32,6 +32,7 @@ import android.util.Log;
import android.util.Slog;
import java.util.List;
+import java.util.function.Consumer;
/** Utility methods for accessing {@link RoleManager} APIs. */
@SuppressLint("LongLogTag")
@@ -46,7 +47,8 @@ final class RolesUtils {
}
static void addRoleHolderForAssociation(
- @NonNull Context context, @NonNull AssociationInfo associationInfo) {
+ @NonNull Context context, @NonNull AssociationInfo associationInfo,
+ @NonNull Consumer<Boolean> roleGrantResult) {
if (DEBUG) {
Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
}
@@ -62,12 +64,7 @@ final class RolesUtils {
roleManager.addRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName
- + " to the list of " + deviceProfile + " holders.");
- }
- });
+ roleGrantResult);
}
static void removeRoleHolderForAssociation(
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index fb8c5b1a9397..41b62e639ad3 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -363,7 +363,9 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
@Override
public void handleMessage(@NonNull Message msg) {
final int associationId = msg.what;
- onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ if (mSimulated.contains(associationId)) {
+ onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ }
}
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index b3c3a4d3a192..5e8291fe5587 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,3 +2,4 @@ set noparent
ogunwale@google.com
michaelwr@google.com
+vladokom@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 83d527e1d68b..0cf79153ce77 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -361,7 +361,7 @@ class StorageManagerService extends IStorageManager.Stub
private volatile boolean mChargingRequired;
private volatile int mMinGCSleepTime;
private volatile int mTargetDirtyRatio;
- private volatile boolean mNeedGC;
+ private volatile boolean mNeedGC = true;
private volatile boolean mPassedLifetimeThresh;
// Tracking storage write amounts in one period
diff --git a/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 2c9ef4f3c059..b7f8d38171a2 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -72,6 +72,8 @@ import java.util.Set;
* Loads global system configuration info.
* Note: Initializing this class hits the disk and is slow. This class should generally only be
* accessed by the system_server process.
+ *
+ * @hide
*/
public class SystemConfig {
static final String TAG = "SystemConfig";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 593e21a54f15..1a4da7db54e9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3937,7 +3937,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Clear its scheduled jobs
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
// Clearing data is a user-initiated action.
- js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER,
+ js.cancelJobsForUid(appInfo.uid, /* includeProxiedJobs */ true,
+ JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED, "clear data");
// Clear its pending alarms
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
index 35b8165b10c4..e5b034415824 100644
--- a/services/core/java/com/android/server/attention/TEST_MAPPING
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -7,7 +7,7 @@
"include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
},
{
- "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+ "include-filter": "android.voiceinteraction.cts.unittests.HotwordDetectedResultTest"
},
{
"include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index 62f94ed05e0a..1a5f31c8ac90 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -30,7 +30,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/** Probe for ambient light. */
final class ALSProbe implements Probe {
@@ -47,12 +50,18 @@ final class ALSProbe implements Probe {
private boolean mEnabled = false;
private boolean mDestroyed = false;
+ private boolean mDestroyRequested = false;
+ private boolean mDisableRequested = false;
+ private volatile NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
mLastAmbientLux = event.values[0];
+ if (mNextConsumer != null) {
+ completeNextConsumer(mLastAmbientLux);
+ }
}
@Override
@@ -102,29 +111,84 @@ final class ALSProbe implements Probe {
@Override
public synchronized void enable() {
- if (!mDestroyed) {
+ if (!mDestroyed && !mDestroyRequested) {
+ mDisableRequested = false;
enableLightSensorLoggingLocked();
}
}
@Override
public synchronized void disable() {
- if (!mDestroyed) {
+ mDisableRequested = true;
+
+ // if a final consumer is set it will call destroy/disable on the next value if requested
+ if (!mDestroyed && mNextConsumer == null) {
disableLightSensorLoggingLocked();
}
}
@Override
public synchronized void destroy() {
- disable();
- mDestroyed = true;
+ mDestroyRequested = true;
+
+ // if a final consumer is set it will call destroy/disable on the next value if requested
+ if (!mDestroyed && mNextConsumer == null) {
+ disable();
+ mDestroyed = true;
+ }
}
/** The most recent lux reading. */
- public float getCurrentLux() {
+ public float getMostRecentLux() {
return mLastAmbientLux;
}
+ /**
+ * Register a listener for the next available ALS reading, which will be reported to the given
+ * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value
+ * is available.
+ *
+ * This method is intended to be used for event logs that occur when the screen may be
+ * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn
+ * on the sensor (if needed), fetch & report the first value, and then destroy or disable this
+ * probe (if needed).
+ *
+ * @param consumer consumer to notify when the data is available
+ * @param handler handler for notifying the consumer, or null
+ */
+ public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer,
+ @Nullable Handler handler) {
+ final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
+ final float current = mLastAmbientLux;
+ if (current > 0) {
+ nextConsumer.consume(current);
+ } else if (mDestroyed) {
+ nextConsumer.consume(-1f);
+ } else if (mNextConsumer != null) {
+ mNextConsumer.add(nextConsumer);
+ } else {
+ mNextConsumer = nextConsumer;
+ enableLightSensorLoggingLocked();
+ }
+ }
+
+ private synchronized void completeNextConsumer(float value) {
+ Slog.v(TAG, "Finishing next consumer");
+
+ final NextConsumer consumer = mNextConsumer;
+ mNextConsumer = null;
+
+ if (mDestroyRequested) {
+ destroy();
+ } else if (mDisableRequested) {
+ disable();
+ }
+
+ if (consumer != null) {
+ consumer.consume(value);
+ }
+ }
+
private void enableLightSensorLoggingLocked() {
if (!mEnabled) {
mEnabled = true;
@@ -160,4 +224,30 @@ final class ALSProbe implements Probe {
+ mLightSensorListener.hashCode());
disable();
}
+
+ private static class NextConsumer {
+ @NonNull private final Consumer<Float> mConsumer;
+ @Nullable private final Handler mHandler;
+ @NonNull private final List<NextConsumer> mOthers = new ArrayList<>();
+
+ private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) {
+ mConsumer = consumer;
+ mHandler = handler;
+ }
+
+ public void consume(float value) {
+ if (mHandler != null) {
+ mHandler.post(() -> mConsumer.accept(value));
+ } else {
+ mConsumer.accept(value);
+ }
+ for (NextConsumer c : mOthers) {
+ c.consume(value);
+ }
+ }
+
+ public void add(NextConsumer consumer) {
+ mOthers.add(consumer);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index d6ca8a68145e..27a70c51f667 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger {
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
public void authenticate(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- int authState, boolean requireConfirmation,
- int targetUserId, float ambientLightLux) {
+ int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
@@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger {
operationContext.isAod);
}
+ /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
+ public void authenticate(OperationContext operationContext,
+ int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
+ int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) {
+ alsProbe.awaitNextLux((ambientLightLux) -> {
+ authenticate(operationContext, statsModality, statsAction, statsClient, isDebug,
+ latency, authState, requireConfirmation, targetUserId, ambientLightLux);
+ }, null /* handler */);
+ }
+
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
public void enroll(int statsModality, int statsAction, int statsClient,
int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index 02b350e97ef8..55fe854e1404 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -220,7 +220,7 @@ public class BiometricLogger {
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
+ ", Latency: " + latency
- + ", Lux: " + mALSProbe.getCurrentLux());
+ + ", Lux: " + mALSProbe.getMostRecentLux());
} else {
Slog.v(TAG, "Authentication latency: " + latency);
}
@@ -231,7 +231,7 @@ public class BiometricLogger {
mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux());
+ latency, authState, requireConfirmation, targetUserId, mALSProbe);
}
/** Log enrollment outcome. */
@@ -245,7 +245,7 @@ public class BiometricLogger {
+ ", User: " + targetUserId
+ ", Client: " + mStatsClient
+ ", Latency: " + latency
- + ", Lux: " + mALSProbe.getCurrentLux()
+ + ", Lux: " + mALSProbe.getMostRecentLux()
+ ", Success: " + enrollSuccessful);
} else {
Slog.v(TAG, "Enroll latency: " + latency);
@@ -256,7 +256,7 @@ public class BiometricLogger {
}
mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
- targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux());
+ targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
}
/** Report unexpected enrollment reported by the HAL. */
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 e0955b7dd450..5e6a025e5229 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
@@ -333,6 +333,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mALSProbeCallback.getProbe().disable();
}
});
+ if (getBiometricContext().isAwake()) {
+ mALSProbeCallback.getProbe().enable();
+ }
if (session.hasContextMethods()) {
return session.getSession().authenticateWithContext(mOperationId, opContext);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 23c020eb8692..3c1bf0b8c6aa 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1138,10 +1138,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mHandler.removeCallbacksAndMessages(null);
// Release any outstanding wakelocks we're still holding because of pending messages.
- mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
- mWakelockController.releaseStateChangedSuspendBlocker();
- mWakelockController.releaseProxPositiveSuspendBlocker();
- mWakelockController.releaseProxNegativeSuspendBlocker();
+ mWakelockController.releaseAll();
final float brightness = mPowerState != null
? mPowerState.getScreenBrightness()
@@ -1729,7 +1726,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Grab a wake lock if we have unfinished business.
if (!finished) {
- mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
}
// Notify the power manager when ready.
@@ -1749,7 +1746,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Release the wake lock when we have no unfinished business.
if (finished) {
- mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+ mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
}
// Record if dozing for future comparison.
@@ -2248,7 +2245,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mSensorManager.unregisterListener(mProximitySensorListener);
// release wake lock(must be last)
boolean proxDebounceSuspendBlockerReleased =
- mWakelockController.releaseProxDebounceSuspendBlocker();
+ mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
if (proxDebounceSuspendBlockerReleased) {
mPendingProximityDebounceTime = -1;
}
@@ -2272,11 +2270,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (positive) {
mPendingProximity = PROXIMITY_POSITIVE;
mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
- mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+ mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
} else {
mPendingProximity = PROXIMITY_NEGATIVE;
mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
- mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+ mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
}
// Debounce the new sensor reading.
@@ -2300,7 +2300,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
updatePowerState();
// (must be last)
boolean proxDebounceSuspendBlockerReleased =
- mWakelockController.releaseProxDebounceSuspendBlocker();
+ mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
if (proxDebounceSuspendBlockerReleased) {
mPendingProximityDebounceTime = -1;
}
@@ -2315,7 +2316,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
private void sendOnStateChangedWithWakelock() {
- boolean wakeLockAcquired = mWakelockController.acquireStateChangedSuspendBlocker();
+ boolean wakeLockAcquired = mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_STATE_CHANGED);
if (wakeLockAcquired) {
mHandler.post(mWakelockController.getOnStateChangedRunnable());
}
@@ -2479,13 +2481,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
private void sendOnProximityPositiveWithWakelock() {
- mWakelockController.acquireProxPositiveSuspendBlocker();
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
}
private void sendOnProximityNegativeWithWakelock() {
- mWakelockController.acquireProxNegativeSuspendBlocker();
+ mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
}
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index cbf559fdb84f..6511f4f4fa84 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -16,12 +16,15 @@
package com.android.server.display;
+import android.annotation.IntDef;
import android.hardware.display.DisplayManagerInternal;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* A utility class to acquire/release suspend blockers and manage appropriate states around it.
@@ -29,8 +32,26 @@ import java.io.PrintWriter;
* display states as needed.
*/
public final class WakelockController {
+ public static final int WAKE_LOCK_PROXIMITY_POSITIVE = 1;
+ public static final int WAKE_LOCK_PROXIMITY_NEGATIVE = 2;
+ public static final int WAKE_LOCK_PROXIMITY_DEBOUNCE = 3;
+ public static final int WAKE_LOCK_STATE_CHANGED = 4;
+ public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 5;
+
+ private static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
private static final boolean DEBUG = false;
+ @IntDef(flag = true, prefix = "WAKE_LOCK_", value = {
+ WAKE_LOCK_PROXIMITY_POSITIVE,
+ WAKE_LOCK_PROXIMITY_NEGATIVE,
+ WAKE_LOCK_PROXIMITY_DEBOUNCE,
+ WAKE_LOCK_STATE_CHANGED,
+ WAKE_LOCK_UNFINISHED_BUSINESS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WAKE_LOCK_TYPE {
+ }
+
// Asynchronous callbacks into the power manager service.
// Only invoked from the handler thread while no locks are held.
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
@@ -58,17 +79,17 @@ public final class WakelockController {
// (i.e. DisplayPowerController2).
private boolean mOnStateChangedPending;
- // Count of positive proximity messages currently held. Used to keep track of how many
- // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
- // Should only be accessed on the Handler thread of the class managing the Display states
- // (i.e. DisplayPowerController2).
- private int mOnProximityPositiveMessages;
+ // When true, it implies that a positive proximity wakelock is currently held. Used to keep
+ // track if suspend blocker acquisitions is pending when shutting down the
+ // DisplayPowerController2. Should only be accessed on the Handler thread of the class
+ // managing the Display states (i.e. DisplayPowerController2).
+ private boolean mIsProximityPositiveAcquired;
- // Count of negative proximity messages currently held. Used to keep track of how many
- // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
- // Should only be accessed on the Handler thread of the class managing the Display states
- // (i.e. DisplayPowerController2).
- private int mOnProximityNegativeMessages;
+ // When true, it implies that a negative proximity wakelock is currently held. Used to keep
+ // track if suspend blocker acquisitions is pending when shutting down the
+ // DisplayPowerController2. Should only be accessed on the Handler thread of the class
+ // managing the Display states (i.e. DisplayPowerController2).
+ private boolean mIsProximityNegativeAcquired;
/**
* The constructor of WakelockController. Manages the initialization of all the local entities
@@ -87,9 +108,86 @@ public final class WakelockController {
}
/**
+ * A utility to acquire a wakelock
+ *
+ * @param wakelock The type of Wakelock to be acquired
+ * @return True of the wakelock is successfully acquired. False if it is already acquired
+ */
+ public boolean acquireWakelock(@WAKE_LOCK_TYPE int wakelock) {
+ return acquireWakelockInternal(wakelock);
+ }
+
+ /**
+ * A utility to release a wakelock
+ *
+ * @param wakelock The type of Wakelock to be released
+ * @return True of an acquired wakelock is successfully released. False if it is already
+ * acquired
+ */
+ public boolean releaseWakelock(@WAKE_LOCK_TYPE int wakelock) {
+ return releaseWakelockInternal(wakelock);
+ }
+
+ /**
+ * A utility to release all the wakelock acquired by the system
+ */
+ public void releaseAll() {
+ for (int i = WAKE_LOCK_PROXIMITY_POSITIVE; i < WAKE_LOCK_MAX; i++) {
+ releaseWakelockInternal(i);
+ }
+ }
+
+ private boolean acquireWakelockInternal(@WAKE_LOCK_TYPE int wakelock) {
+ switch (wakelock) {
+ case WAKE_LOCK_PROXIMITY_POSITIVE:
+ return acquireProxPositiveSuspendBlocker();
+ case WAKE_LOCK_PROXIMITY_NEGATIVE:
+ return acquireProxNegativeSuspendBlocker();
+ case WAKE_LOCK_PROXIMITY_DEBOUNCE:
+ return acquireProxDebounceSuspendBlocker();
+ case WAKE_LOCK_STATE_CHANGED:
+ return acquireStateChangedSuspendBlocker();
+ case WAKE_LOCK_UNFINISHED_BUSINESS:
+ return acquireUnfinishedBusinessSuspendBlocker();
+ default:
+ throw new RuntimeException("Invalid wakelock attempted to be acquired");
+ }
+ }
+
+ private boolean releaseWakelockInternal(@WAKE_LOCK_TYPE int wakelock) {
+ switch (wakelock) {
+ case WAKE_LOCK_PROXIMITY_POSITIVE:
+ return releaseProxPositiveSuspendBlocker();
+ case WAKE_LOCK_PROXIMITY_NEGATIVE:
+ return releaseProxNegativeSuspendBlocker();
+ case WAKE_LOCK_PROXIMITY_DEBOUNCE:
+ return releaseProxDebounceSuspendBlocker();
+ case WAKE_LOCK_STATE_CHANGED:
+ return releaseStateChangedSuspendBlocker();
+ case WAKE_LOCK_UNFINISHED_BUSINESS:
+ return releaseUnfinishedBusinessSuspendBlocker();
+ default:
+ throw new RuntimeException("Invalid wakelock attempted to be released");
+ }
+ }
+
+ /**
+ * Acquires the proximity positive wakelock and notifies the PowerManagerService about the
+ * changes.
+ */
+ private boolean acquireProxPositiveSuspendBlocker() {
+ if (!mIsProximityPositiveAcquired) {
+ mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
+ mIsProximityPositiveAcquired = true;
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Acquires the state change wakelock and notifies the PowerManagerService about the changes.
*/
- public boolean acquireStateChangedSuspendBlocker() {
+ private boolean acquireStateChangedSuspendBlocker() {
// Grab a wake lock if we have change of the display state
if (!mOnStateChangedPending) {
if (DEBUG) {
@@ -105,18 +203,20 @@ public final class WakelockController {
/**
* Releases the state change wakelock and notifies the PowerManagerService about the changes.
*/
- public void releaseStateChangedSuspendBlocker() {
+ private boolean releaseStateChangedSuspendBlocker() {
if (mOnStateChangedPending) {
mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
mOnStateChangedPending = false;
+ return true;
}
+ return false;
}
/**
* Acquires the unfinished business wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void acquireUnfinishedBusinessSuspendBlocker() {
+ private boolean acquireUnfinishedBusinessSuspendBlocker() {
// Grab a wake lock if we have unfinished business.
if (!mUnfinishedBusiness) {
if (DEBUG) {
@@ -124,79 +224,84 @@ public final class WakelockController {
}
mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
mUnfinishedBusiness = true;
+ return true;
}
+ return false;
}
/**
* Releases the unfinished business wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void releaseUnfinishedBusinessSuspendBlocker() {
+ private boolean releaseUnfinishedBusinessSuspendBlocker() {
if (mUnfinishedBusiness) {
if (DEBUG) {
Slog.d(mTag, "Finished business...");
}
mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
mUnfinishedBusiness = false;
+ return true;
}
- }
-
- /**
- * Acquires the proximity positive wakelock and notifies the PowerManagerService about the
- * changes.
- */
- public void acquireProxPositiveSuspendBlocker() {
- mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
- mOnProximityPositiveMessages++;
+ return false;
}
/**
* Releases the proximity positive wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void releaseProxPositiveSuspendBlocker() {
- for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+ private boolean releaseProxPositiveSuspendBlocker() {
+ if (mIsProximityPositiveAcquired) {
mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ mIsProximityPositiveAcquired = false;
+ return true;
}
- mOnProximityPositiveMessages = 0;
+ return false;
}
/**
* Acquires the proximity negative wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void acquireProxNegativeSuspendBlocker() {
- mOnProximityNegativeMessages++;
- mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+ private boolean acquireProxNegativeSuspendBlocker() {
+ if (!mIsProximityNegativeAcquired) {
+ mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+ mIsProximityNegativeAcquired = true;
+ return true;
+ }
+ return false;
}
/**
* Releases the proximity negative wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void releaseProxNegativeSuspendBlocker() {
- for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+ private boolean releaseProxNegativeSuspendBlocker() {
+ if (mIsProximityNegativeAcquired) {
mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ mIsProximityNegativeAcquired = false;
+ return true;
}
- mOnProximityNegativeMessages = 0;
+ return false;
}
/**
* Acquires the proximity debounce wakelock and notifies the PowerManagerService about the
* changes.
*/
- public void acquireProxDebounceSuspendBlocker() {
+ private boolean acquireProxDebounceSuspendBlocker() {
if (!mHasProximityDebounced) {
mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
+ mHasProximityDebounced = true;
+ return true;
}
- mHasProximityDebounced = true;
+ return false;
}
/**
* Releases the proximity debounce wakelock and notifies the PowerManagerService about the
* changes.
*/
- public boolean releaseProxDebounceSuspendBlocker() {
+ private boolean releaseProxDebounceSuspendBlocker() {
if (mHasProximityDebounced) {
mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
mHasProximityDebounced = false;
@@ -210,9 +315,11 @@ public final class WakelockController {
*/
public Runnable getOnProximityPositiveRunnable() {
return () -> {
- mOnProximityPositiveMessages--;
- mDisplayPowerCallbacks.onProximityPositive();
- mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ if (mIsProximityPositiveAcquired) {
+ mIsProximityPositiveAcquired = false;
+ mDisplayPowerCallbacks.onProximityPositive();
+ mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ }
};
}
@@ -221,9 +328,11 @@ public final class WakelockController {
*/
public Runnable getOnStateChangedRunnable() {
return () -> {
- mOnStateChangedPending = false;
- mDisplayPowerCallbacks.onStateChanged();
- mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ if (mOnStateChangedPending) {
+ mOnStateChangedPending = false;
+ mDisplayPowerCallbacks.onStateChanged();
+ mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ }
};
}
@@ -232,9 +341,11 @@ public final class WakelockController {
*/
public Runnable getOnProximityNegativeRunnable() {
return () -> {
- mOnProximityNegativeMessages--;
- mDisplayPowerCallbacks.onProximityNegative();
- mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ if (mIsProximityNegativeAcquired) {
+ mIsProximityNegativeAcquired = false;
+ mDisplayPowerCallbacks.onProximityNegative();
+ mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ }
};
}
@@ -246,8 +357,8 @@ public final class WakelockController {
pw.println(" mDisplayId=" + mDisplayId);
pw.println(" mUnfinishedBusiness=" + hasUnfinishedBusiness());
pw.println(" mOnStateChangePending=" + isOnStateChangedPending());
- pw.println(" mOnProximityPositiveMessages=" + getOnProximityPositiveMessages());
- pw.println(" mOnProximityNegativeMessages=" + getOnProximityNegativeMessages());
+ pw.println(" mOnProximityPositiveMessages=" + isProximityPositiveAcquired());
+ pw.println(" mOnProximityNegativeMessages=" + isProximityNegativeAcquired());
}
@VisibleForTesting
@@ -286,13 +397,13 @@ public final class WakelockController {
}
@VisibleForTesting
- int getOnProximityPositiveMessages() {
- return mOnProximityPositiveMessages;
+ boolean isProximityPositiveAcquired() {
+ return mIsProximityPositiveAcquired;
}
@VisibleForTesting
- int getOnProximityNegativeMessages() {
- return mOnProximityNegativeMessages;
+ boolean isProximityNegativeAcquired() {
+ return mIsProximityNegativeAcquired;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 848156047f0d..605a2038339c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -69,7 +69,6 @@ import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -337,7 +336,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Ongoing notification
private NotificationManager mNotificationManager;
- KeyguardManager mKeyguardManager;
@Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
private final Notification.Builder mImeSwitcherNotification;
private final PendingIntent mImeSwitchPendingIntent;
@@ -1941,7 +1939,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final int currentUserId = mSettings.getCurrentUserId();
mSettings.switchCurrentUser(currentUserId,
!mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId));
- mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
@@ -3005,7 +3002,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!mShowOngoingImeSwitcherForPhones) return false;
if (mMenuController.getSwitchingDialogLocked() != null) return false;
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
+ && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
+ return false;
+ }
if ((visibility & InputMethodService.IME_ACTIVE) == 0
|| (visibility & InputMethodService.IME_INVISIBLE) != 0) {
return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index a25630ffefa1..c212e8e3c82c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -21,7 +21,6 @@ import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
import android.annotation.Nullable;
import android.app.AlertDialog;
-import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
@@ -56,7 +55,6 @@ final class InputMethodMenuController {
private final InputMethodUtils.InputMethodSettings mSettings;
private final InputMethodSubtypeSwitchingController mSwitchingController;
private final ArrayMap<String, InputMethodInfo> mMethodMap;
- private final KeyguardManager mKeyguardManager;
private final WindowManagerInternal mWindowManagerInternal;
private AlertDialog.Builder mDialogBuilder;
@@ -76,7 +74,6 @@ final class InputMethodMenuController {
mSettings = mService.mSettings;
mSwitchingController = mService.mSwitchingController;
mMethodMap = mService.mMethodMap;
- mKeyguardManager = mService.mKeyguardManager;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
}
@@ -209,8 +206,8 @@ final class InputMethodMenuController {
}
private boolean isScreenLocked() {
- return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
- && mKeyguardManager.isKeyguardSecure();
+ return mWindowManagerInternal.isKeyguardLocked()
+ && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId());
}
void updateKeyboardFromSettingsLocked() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 58d677c31606..c899cf29cc3e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -256,10 +256,10 @@ public class LockSettingsService extends ILockSettings.Stub {
@GuardedBy("mUserCreationAndRemovalLock")
private boolean mBootComplete;
- // Current password metric for all users on the device. Updated when user unlocks
- // the device or changes password. Removed when user is stopped.
+ // Current password metrics for all secured users on the device. Updated when user unlocks the
+ // device or changes password. Removed when user is stopped.
@GuardedBy("this")
- final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
+ private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
@VisibleForTesting
protected boolean mHasSecureLockScreen;
@@ -2274,8 +2274,11 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
+ private @Nullable PasswordMetrics loadPasswordMetrics(SyntheticPassword sp, int userHandle) {
synchronized (mSpManager) {
+ if (!isUserSecure(userHandle)) {
+ return null;
+ }
return mSpManager.getPasswordMetrics(sp, getCurrentLskfBasedProtectorId(userHandle),
userHandle);
}
@@ -2703,14 +2706,13 @@ public class LockSettingsService extends ILockSettings.Stub {
return handle;
}
- private void onCredentialVerified(SyntheticPassword sp, PasswordMetrics metrics, int userId) {
+ private void onCredentialVerified(SyntheticPassword sp, @Nullable PasswordMetrics metrics,
+ int userId) {
if (metrics != null) {
synchronized (this) {
mUserPasswordMetrics.put(userId, metrics);
}
- } else {
- Slog.wtf(TAG, "Null metrics after credential verification");
}
unlockKeystore(sp.deriveKeyStorePassword(), userId);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ab69c3426d04..3fd488e6d357 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -43,6 +43,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
@@ -97,9 +98,11 @@ import java.util.Set;
* For each protector, stored under the corresponding protector ID:
* SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value). Always exists.
* PASSWORD_DATA_NAME: Data used for LSKF verification, such as the scrypt salt and
- * parameters. Only exists for LSKF-based protectors.
+ * parameters. Only exists for LSKF-based protectors. Doesn't exist when
+ * the LSKF is empty, except in old protectors.
* PASSWORD_METRICS_NAME: Metrics about the LSKF, encrypted by a key derived from the SP.
- * Only exists for LSKF-based protectors.
+ * Only exists for LSKF-based protectors. Doesn't exist when the LSKF
+ * is empty, except in old protectors.
* SECDISCARDABLE_NAME: A large number of random bytes that all need to be known in order to
* decrypt SP_BLOB_NAME. When the protector is deleted, this file is
* overwritten and deleted as a "best-effort" attempt to support secure
@@ -333,24 +336,15 @@ public class SyntheticPasswordManager {
byte scryptLogP;
public int credentialType;
byte[] salt;
- // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF,
- // when applicable. This field isn't used if Weaver is available, or in new protectors when
- // the LSKF is empty.
+ // When Weaver is unavailable, this is the Gatekeeper password handle that resulted from
+ // enrolling the stretched LSKF.
public byte[] passwordHandle;
public static PasswordData create(int credentialType) {
PasswordData result = new PasswordData();
- if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- // When the LSKF is empty, scrypt provides no security benefit, so just use the
- // minimum parameters (N=2, r=1, p=1).
- result.scryptLogN = 1;
- result.scryptLogR = 0;
- result.scryptLogP = 0;
- } else {
- result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
- result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
- result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
- }
+ result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
+ result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
+ result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
result.credentialType = credentialType;
result.salt = secureRandom(PASSWORD_SALT_LENGTH);
return result;
@@ -611,7 +605,6 @@ public class SyntheticPasswordManager {
int getCredentialType(long protectorId, int userId) {
byte[] passwordData = loadState(PASSWORD_DATA_NAME, protectorId, userId);
if (passwordData == null) {
- Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
return LockPatternUtils.CREDENTIAL_TYPE_NONE;
}
return PasswordData.fromBytes(passwordData).credentialType;
@@ -783,7 +776,8 @@ public class SyntheticPasswordManager {
public long createLskfBasedProtector(IGateKeeperService gatekeeper,
LockscreenCredential credential, SyntheticPassword sp, int userId) {
long protectorId = generateProtectorId();
- PasswordData pwd = PasswordData.create(credential.getType());
+ // There's no need to store password data about an empty LSKF.
+ PasswordData pwd = credential.isNone() ? null : PasswordData.create(credential.getType());
byte[] stretchedLskf = stretchLskf(credential, pwd);
long sid = GateKeeper.INVALID_SECURE_USER_ID;
final byte[] protectorSecret;
@@ -837,8 +831,10 @@ public class SyntheticPasswordManager {
// No need to pass in quality since the credential type already encodes sufficient info
synchronizeFrpPassword(pwd, 0, userId);
}
- saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
- savePasswordMetrics(credential, sp, protectorId, userId);
+ if (!credential.isNone()) {
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+ savePasswordMetrics(credential, sp, protectorId, userId);
+ }
createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
sid, userId);
return protectorId;
@@ -883,26 +879,26 @@ public class SyntheticPasswordManager {
public void migrateFrpPasswordLocked(long protectorId, UserInfo userInfo,
int requestedQuality) {
if (mStorage.getPersistentDataBlockManager() != null
- && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
+ && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)
+ && getCredentialType(protectorId, userInfo.id) !=
+ LockPatternUtils.CREDENTIAL_TYPE_NONE) {
PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
userInfo.id));
- if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- int weaverSlot = loadWeaverSlot(protectorId, userInfo.id);
- if (weaverSlot != INVALID_WEAVER_SLOT) {
- synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
- } else {
- synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
- }
+ int weaverSlot = loadWeaverSlot(protectorId, userInfo.id);
+ if (weaverSlot != INVALID_WEAVER_SLOT) {
+ synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
+ } else {
+ synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
}
}
}
- private void synchronizeFrpPassword(PasswordData pwd,
- int requestedQuality, int userId) {
+ private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
+ int userId) {
if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext,
mUserManager.getUserInfo(userId))) {
- if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
pwd.toBytes());
} else {
@@ -911,12 +907,12 @@ public class SyntheticPasswordManager {
}
}
- private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
- int weaverSlot) {
+ private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
+ int userId, int weaverSlot) {
if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext,
mUserManager.getUserInfo(userId))) {
- if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
requestedQuality, pwd.toBytes());
} else {
@@ -1047,12 +1043,20 @@ public class SyntheticPasswordManager {
return result;
}
- PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
- userId));
-
- if (!credential.checkAgainstStoredType(pwd.credentialType)) {
+ // Load the PasswordData file. If it doesn't exist, then the LSKF is empty (i.e.,
+ // CREDENTIAL_TYPE_NONE), and we'll skip the scrypt and Gatekeeper steps. If it exists,
+ // then either the LSKF is nonempty, or it's an old protector that uses scrypt and
+ // Gatekeeper even though the LSKF is empty.
+ byte[] pwdDataBytes = loadState(PASSWORD_DATA_NAME, protectorId, userId);
+ PasswordData pwd = null;
+ int storedType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ if (pwdDataBytes != null) {
+ pwd = PasswordData.fromBytes(pwdDataBytes);
+ storedType = pwd.credentialType;
+ }
+ if (!credential.checkAgainstStoredType(storedType)) {
Slog.e(TAG, TextUtils.formatSimple("Credential type mismatch: expected %d actual %d",
- pwd.credentialType, credential.getType()));
+ storedType, credential.getType()));
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
@@ -1078,7 +1082,7 @@ public class SyntheticPasswordManager {
} else {
// Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless
// the LSKF is empty in which case Gatekeeper might not have been used at all.
- if (pwd.passwordHandle == null) {
+ if (pwd == null || pwd.passwordHandle == null) {
if (!credential.isNone()) {
Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF");
result.gkResponse = VerifyCredentialResponse.ERROR;
@@ -1149,7 +1153,8 @@ public class SyntheticPasswordManager {
// Upgrade case: store the metrics if the device did not have stored metrics before, should
// only happen once on old protectors.
- if (result.syntheticPassword != null && !hasPasswordMetrics(protectorId, userId)) {
+ if (result.syntheticPassword != null && !credential.isNone() &&
+ !hasPasswordMetrics(protectorId, userId)) {
savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
}
return result;
@@ -1415,6 +1420,11 @@ public class SyntheticPasswordManager {
}
}
+ @VisibleForTesting
+ boolean hasPasswordData(long protectorId, int userId) {
+ return hasState(PASSWORD_DATA_NAME, protectorId, userId);
+ }
+
/**
* Retrieves a user's saved password metrics from their LSKF-based SP protector. The
* SyntheticPassword itself is needed to decrypt the file containing the password metrics.
@@ -1422,10 +1432,16 @@ public class SyntheticPasswordManager {
public @Nullable PasswordMetrics getPasswordMetrics(SyntheticPassword sp, long protectorId,
int userId) {
final byte[] encrypted = loadState(PASSWORD_METRICS_NAME, protectorId, userId);
- if (encrypted == null) return null;
+ if (encrypted == null) {
+ Slogf.e(TAG, "Failed to read password metrics file for user %d", userId);
+ return null;
+ }
final byte[] decrypted = SyntheticPasswordCrypto.decrypt(sp.deriveMetricsKey(),
/* personalization= */ new byte[0], encrypted);
- if (decrypted == null) return null;
+ if (decrypted == null) {
+ Slogf.e(TAG, "Failed to decrypt password metrics file for user %d", userId);
+ return null;
+ }
return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
}
@@ -1437,7 +1453,8 @@ public class SyntheticPasswordManager {
saveState(PASSWORD_METRICS_NAME, encrypted, protectorId, userId);
}
- private boolean hasPasswordMetrics(long protectorId, int userId) {
+ @VisibleForTesting
+ boolean hasPasswordMetrics(long protectorId, int userId) {
return hasState(PASSWORD_METRICS_NAME, protectorId, userId);
}
@@ -1500,8 +1517,23 @@ public class SyntheticPasswordManager {
return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
}
- private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) {
+ /**
+ * Stretches <code>credential</code>, if needed, using the parameters from <code>data</code>.
+ * <p>
+ * When the credential is empty, stetching provides no security benefit. Thus, new protectors
+ * for an empty credential use <code>null</code> {@link PasswordData} and skip the stretching.
+ * <p>
+ * However, old protectors always stored {@link PasswordData} and did the stretching, regardless
+ * of whether the credential was empty or not. For this reason, this method also continues to
+ * support stretching of empty credentials so that old protectors can still be unlocked.
+ */
+ @VisibleForTesting
+ byte[] stretchLskf(LockscreenCredential credential, @Nullable PasswordData data) {
final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
+ if (data == null) {
+ Preconditions.checkArgument(credential.isNone());
+ return Arrays.copyOf(password, STRETCHED_LSKF_LENGTH);
+ }
return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR,
1 << data.scryptLogP, STRETCHED_LSKF_LENGTH);
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 346f3113f25f..58428ca48c97 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -43,6 +43,7 @@ import com.android.server.SystemConfig;
import java.io.FileDescriptor;
import java.util.Objects;
+import java.util.OptionalInt;
/**
* Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -60,6 +61,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportWhitelistedPackages;
+ @GuardedBy("mLock")
+ private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+
BugreportManagerServiceImpl(Context context) {
mContext = context;
mAppOps = context.getSystemService(AppOpsManager.class);
@@ -70,13 +74,25 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
+ public void preDumpUiData(String callingPackage) {
+ enforcePermission(callingPackage, Binder.getCallingUid(), true);
+
+ synchronized (mLock) {
+ preDumpUiDataLocked(callingPackage);
+ }
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(int callingUidUnused, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
- int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+ int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+ boolean isScreenshotRequested) {
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(bugreportFd);
Objects.requireNonNull(listener);
validateBugreportMode(bugreportMode);
+ validateBugreportFlags(bugreportFlags);
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, bugreportMode
@@ -90,7 +106,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
synchronized (mLock) {
startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
- bugreportMode, listener, isScreenshotRequested);
+ bugreportMode, bugreportFlags, listener, isScreenshotRequested);
}
}
@@ -108,17 +124,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
try {
// Note: this may throw SecurityException back out to the caller if they aren't
- // allowed to cancel the report, in which case we should NOT be setting ctl.stop,
- // since that would unintentionally kill some other app's bugreport, which we
- // specifically disallow.
+ // allowed to cancel the report, in which case we should NOT stop the dumpstate
+ // service, since that would unintentionally kill some other app's bugreport, which
+ // we specifically disallow.
ds.cancelBugreport(callingUid, callingPackage);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in cancelBugreport", e);
}
- // This tells init to cancel bugreportd service. Note that this is achieved through
- // setting a system property which is not thread-safe. So the lock here offers
- // thread-safety only among callers of the API.
- SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ stopDumpstateBinderServiceLocked();
}
}
@@ -134,6 +147,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
+ private void validateBugreportFlags(int flags) {
+ flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ if (flags != 0) {
+ Slog.w(TAG, "Unknown bugreport flags: " + flags);
+ throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
+ }
+ }
+
private void enforcePermission(
String callingPackage, int callingUid, boolean checkCarrierPrivileges) {
mAppOps.checkPackage(callingUid, callingPackage);
@@ -224,17 +245,62 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
@GuardedBy("mLock")
+ private void preDumpUiDataLocked(String callingPackage) {
+ mPreDumpedDataUid = OptionalInt.empty();
+
+ if (isDumpstateBinderServiceRunningLocked()) {
+ Slog.e(TAG, "'dumpstate' is already running. "
+ + "Cannot pre-dump data while another operation is currently in progress.");
+ return;
+ }
+
+ IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+ if (ds == null) {
+ Slog.e(TAG, "Unable to get bugreport service");
+ return;
+ }
+
+ try {
+ ds.preDumpUiData(callingPackage);
+ } catch (RemoteException e) {
+ return;
+ } finally {
+ // dumpstate service is already started now. We need to kill it to manage the
+ // lifecycle correctly. If we don't subsequent callers will get
+ // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
+ stopDumpstateBinderServiceLocked();
+ }
+
+
+ mPreDumpedDataUid = OptionalInt.of(Binder.getCallingUid());
+ }
+
+ @GuardedBy("mLock")
private void startBugreportLocked(int callingUid, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
- int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+ int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+ boolean isScreenshotRequested) {
if (isDumpstateBinderServiceRunningLocked()) {
Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
- + " while another one is currently in progress.");
- reportError(listener,
- IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ + " while another operation is currently in progress.");
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
return;
}
+ if ((bugreportFlags & BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA) != 0) {
+ if (mPreDumpedDataUid.isEmpty()) {
+ bugreportFlags = clearBugreportFlag(bugreportFlags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+ + " No pre-dumped data is available.");
+ } else if (mPreDumpedDataUid.getAsInt() != callingUid) {
+ bugreportFlags = clearBugreportFlag(bugreportFlags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+ + " Data was pre-dumped by a different UID.");
+ }
+ }
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -245,10 +311,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
// Wrap the listener so we can intercept binder events directly.
IDumpstateListener myListener = new DumpstateListener(listener, ds);
try {
- ds.startBugreport(callingUid, callingPackage,
- bugreportFd, screenshotFd, bugreportMode, myListener, isScreenshotRequested);
+ ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
+ bugreportFlags, myListener, isScreenshotRequested);
} catch (RemoteException e) {
- // bugreportd service is already started now. We need to kill it to manage the
+ // dumpstate service is already started now. We need to kill it to manage the
// lifecycle correctly. If we don't subsequent callers will get
// BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
// Note that listener will be notified by the death recipient below.
@@ -309,6 +375,19 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
return ds;
}
+ @GuardedBy("mLock")
+ private void stopDumpstateBinderServiceLocked() {
+ // This tells init to cancel bugreportd service. Note that this is achieved through
+ // setting a system property which is not thread-safe. So the lock here offers
+ // thread-safety only among callers of the API.
+ SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ }
+
+ private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
+ flags &= ~flag;
+ return flags;
+ }
+
private void reportError(IDumpstateListener listener, int errorCode) {
try {
listener.onError(errorCode);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index be3a4dab6152..6b3155504f5f 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -54,6 +54,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.pkg.AndroidPackage;
@@ -1005,11 +1006,14 @@ public class PackageInfoUtils {
/** @see ApplicationInfo#privateFlagsExt */
public static int appInfoPrivateFlagsExt(AndroidPackage pkg,
@Nullable PackageStateInternal pkgSetting) {
+ var isAllowlistedForHiddenApis = SystemConfig.getInstance().getHiddenApiWhitelistedApps()
+ .contains(pkg.getPackageName());
// @formatter:off
int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
| flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
| flag(pkg.areAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
- | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK);
+ | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
+ | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
return appInfoPrivateFlagsExt(pkgWithoutStateFlags, pkgSetting);
// @formatter:on
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 78b657bbb906..2232aa1be76f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4066,7 +4066,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// to the restarted activity.
nowVisible = mVisibleRequested;
}
- mTransitionController.requestCloseTransitionIfNeeded(this);
+ // upgrade transition trigger to task if this is the last activity since it means we are
+ // closing the task.
+ final WindowContainer trigger = remove && task != null && task.getChildCount() == 1
+ ? task : this;
+ mTransitionController.requestCloseTransitionIfNeeded(trigger);
cleanUp(true /* cleanServices */, true /* setState */);
if (remove) {
if (mStartingData != null && mVisible && task != null) {
@@ -7636,6 +7640,31 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
}
+ /**
+ * Returns the requested {@link Configuration.Orientation} for the current activity.
+ *
+ * <p>When The current orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns the
+ * requested orientation for the activity below which is the first activity with an explicit
+ * (different from {@link SCREEN_ORIENTATION_UNSET}) orientation which is not {@link
+ * SCREEN_ORIENTATION_BEHIND}.
+ */
+ @Configuration.Orientation
+ @Override
+ int getRequestedConfigurationOrientation(boolean forDisplay) {
+ if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
+ // We use Task here because we want to be consistent with what happens in
+ // multi-window mode where other tasks orientations are ignored.
+ final ActivityRecord belowCandidate = task.getActivity(
+ a -> a.mOrientation != SCREEN_ORIENTATION_UNSET && !a.finishing
+ && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, this,
+ false /* includeBoundary */, true /* traverseTopToBottom */);
+ if (belowCandidate != null) {
+ return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
+ }
+ }
+ return super.getRequestedConfigurationOrientation(forDisplay);
+ }
+
@Override
void onCancelFixedRotationTransform(int originalDisplayRotation) {
if (this != mDisplayContent.getLastOrientationSource()) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index ab38ed23ab41..4459d45f60a8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1844,15 +1844,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
flags |= FLAG_TRANSLUCENT;
}
final Task task = wc.asTask();
- if (task != null && task.voiceSession != null) {
- flags |= FLAG_IS_VOICE_INTERACTION;
- }
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
if (topActivity != null && topActivity.mStartingData != null
&& topActivity.mStartingData.hasImeSurface()) {
flags |= FLAG_WILL_IME_SHOWN;
}
+ if (task.voiceSession != null) {
+ flags |= FLAG_IS_VOICE_INTERACTION;
+ }
}
Task parentTask = null;
final ActivityRecord record = wc.asActivityRecord();
@@ -1880,20 +1880,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Whether the container fills its parent Task bounds.
flags |= FLAG_FILLS_TASK;
}
- }
- final DisplayContent dc = wc.asDisplayContent();
- if (dc != null) {
- flags |= FLAG_IS_DISPLAY;
- if (dc.hasAlertWindowSurfaces()) {
- flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+ } else {
+ final DisplayContent dc = wc.asDisplayContent();
+ if (dc != null) {
+ flags |= FLAG_IS_DISPLAY;
+ if (dc.hasAlertWindowSurfaces()) {
+ flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+ }
+ } else if (isWallpaper(wc)) {
+ flags |= FLAG_IS_WALLPAPER;
+ } else if (isInputMethod(wc)) {
+ flags |= FLAG_IS_INPUT_METHOD;
+ } else {
+ // In this condition, the wc can only be WindowToken or DisplayArea.
+ final int type = wc.getWindowType();
+ if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
+ && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
+ flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW;
+ }
}
}
- if (isWallpaper(wc)) {
- flags |= FLAG_IS_WALLPAPER;
- }
- if (isInputMethod(wc)) {
- flags |= FLAG_IS_INPUT_METHOD;
- }
if (occludesKeyguard(wc)) {
flags |= FLAG_OCCLUDES_KEYGUARD;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 77d0f378cee1..ac85c9a36bcc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -126,19 +126,27 @@ class TransitionController {
mTransitionTracer = transitionTracer;
mTransitionPlayerDeath = () -> {
synchronized (mAtm.mGlobalLock) {
- // Clean-up/finish any playing transitions.
- for (int i = 0; i < mPlayingTransitions.size(); ++i) {
- mPlayingTransitions.get(i).cleanUpOnFailure();
- }
- mPlayingTransitions.clear();
- mTransitionPlayer = null;
- mTransitionPlayerProc = null;
- mRemotePlayer.clear();
- mRunningLock.doNotifyLocked();
+ detachPlayer();
}
};
}
+ private void detachPlayer() {
+ if (mTransitionPlayer == null) return;
+ // Clean-up/finish any playing transitions.
+ for (int i = 0; i < mPlayingTransitions.size(); ++i) {
+ mPlayingTransitions.get(i).cleanUpOnFailure();
+ }
+ mPlayingTransitions.clear();
+ if (mCollectingTransition != null) {
+ mCollectingTransition.abort();
+ }
+ mTransitionPlayer = null;
+ mTransitionPlayerProc = null;
+ mRemotePlayer.clear();
+ mRunningLock.doNotifyLocked();
+ }
+
/** @see #createTransition(int, int) */
@NonNull
Transition createTransition(int type) {
@@ -193,7 +201,7 @@ class TransitionController {
if (mTransitionPlayer.asBinder() != null) {
mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
}
- mTransitionPlayer = null;
+ detachPlayer();
}
if (player.asBinder() != null) {
player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1e6c7207e34a..32feb6c98b24 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.ClipData;
import android.content.Context;
import android.graphics.Matrix;
@@ -452,6 +453,15 @@ public abstract class WindowManagerInternal {
public abstract boolean isKeyguardShowingAndNotOccluded();
/**
+ * Return whether the keyguard is secured by a PIN, pattern or password or a SIM card is
+ * currently locked.
+ *
+ * @param userId User ID to be queried about.
+ * @return {@code true} if a PIN, pattern or password is set or a SIM card is locked.
+ */
+ public abstract boolean isKeyguardSecure(@UserIdInt int userId);
+
+ /**
* Gets the frame of a window given its token.
*
* @param token The token.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 961c320dabe6..5ac034b8954f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7763,6 +7763,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public boolean isKeyguardSecure(@UserIdInt int userId) {
+ return mPolicy.isKeyguardSecure(userId);
+ }
+
+ @Override
public void showGlobalActions() {
WindowManagerService.this.showGlobalActions();
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2304ab40aa57..3590e9c27a9e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1128,10 +1128,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final LauncherAppsServiceInternal launcherApps = LocalServices.getService(
LauncherAppsServiceInternal.class);
- launcherApps.startShortcut(caller.mUid, caller.mPid, callingPackage,
- hop.getShortcutInfo().getPackage(), null /* default featureId */,
+ final boolean success = launcherApps.startShortcut(caller.mUid, caller.mPid,
+ callingPackage, hop.getShortcutInfo().getPackage(), null /* featureId */,
hop.getShortcutInfo().getId(), null /* sourceBounds */, launchOpts,
hop.getShortcutInfo().getUserId());
+ if (success) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
break;
}
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7e93d623f3e0..5fa8dcced671 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -168,6 +168,8 @@ cc_defaults {
"android.hardware.memtrack-V1-ndk",
"android.hardware.power@1.0",
"android.hardware.power@1.1",
+ "android.hardware.power@1.2",
+ "android.hardware.power@1.3",
"android.hardware.power-V3-cpp",
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index cb52e5f72d5f..6e82907d4361 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -101,6 +101,13 @@ public class SystemConfigService extends SystemService {
}
return enabledComponent;
}
+
+ @Override
+ public List<ComponentName> getDefaultVrComponents() {
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.QUERY_ALL_PACKAGES,
+ "Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
+ return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
+ }
};
public SystemConfigService(Context context) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 2df6823afab1..20af02e19a3d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -177,15 +177,19 @@ public final class DisplayPowerController2Test {
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
- verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
dpc.stop();
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
- verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
index 288408c84a4f..07a81ffb8e18 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.hardware.display.DisplayManagerInternal;
@@ -33,6 +34,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Callable;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class WakelockControllerTest {
@@ -64,25 +67,18 @@ public final class WakelockControllerTest {
}
@Test
- public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() {
- // Acquire the suspend blocker
- assertTrue(mWakelockController.acquireStateChangedSuspendBlocker());
- assertTrue(mWakelockController.isOnStateChangedPending());
-
- // Try to reacquire
- assertFalse(mWakelockController.acquireStateChangedSuspendBlocker());
- assertTrue(mWakelockController.isOnStateChangedPending());
+ public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_STATE_CHANGED,
+ () -> mWakelockController.isOnStateChangedPending());
// Verify acquire happened only once
verify(mDisplayPowerCallbacks, times(1))
.acquireSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
// Release
- mWakelockController.releaseStateChangedSuspendBlocker();
- assertFalse(mWakelockController.isOnStateChangedPending());
-
- // Try to release again
- mWakelockController.releaseStateChangedSuspendBlocker();
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_STATE_CHANGED,
+ () -> mWakelockController.isOnStateChangedPending());
// Verify release happened only once
verify(mDisplayPowerCallbacks, times(1))
@@ -90,25 +86,18 @@ public final class WakelockControllerTest {
}
@Test
- public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() {
- // Acquire the suspend blocker
- mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
- assertTrue(mWakelockController.hasUnfinishedBusiness());
-
- // Try to reacquire
- mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
- assertTrue(mWakelockController.hasUnfinishedBusiness());
+ public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+ () -> mWakelockController.hasUnfinishedBusiness());
// Verify acquire happened only once
verify(mDisplayPowerCallbacks, times(1))
.acquireSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
- // Release the suspend blocker
- mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
- assertFalse(mWakelockController.hasUnfinishedBusiness());
-
- // Try to release again
- mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+ () -> mWakelockController.hasUnfinishedBusiness());
// Verify release happened only once
verify(mDisplayPowerCallbacks, times(1))
@@ -116,70 +105,56 @@ public final class WakelockControllerTest {
}
@Test
- public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() {
- // Acquire the suspend blocker
- mWakelockController.acquireProxPositiveSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
-
- // Try to reacquire
- mWakelockController.acquireProxPositiveSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityPositiveMessages(), 2);
+ public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+ () -> mWakelockController.isProximityPositiveAcquired());
// Verify acquire happened only once
- verify(mDisplayPowerCallbacks, times(2))
+ verify(mDisplayPowerCallbacks, times(1))
.acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
- // Release the suspend blocker
- mWakelockController.releaseProxPositiveSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityPositiveMessages(), 0);
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+ () -> mWakelockController.isProximityPositiveAcquired());
- // Verify all suspend blockers were released
- verify(mDisplayPowerCallbacks, times(2))
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
.releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
}
@Test
- public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() {
- // Acquire the suspend blocker
- mWakelockController.acquireProxNegativeSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
-
- // Try to reacquire
- mWakelockController.acquireProxNegativeSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityNegativeMessages(), 2);
+ public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+ () -> mWakelockController.isProximityNegativeAcquired());
// Verify acquire happened only once
- verify(mDisplayPowerCallbacks, times(2))
+ verify(mDisplayPowerCallbacks, times(1))
.acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
- // Release the suspend blocker
- mWakelockController.releaseProxNegativeSuspendBlocker();
- assertEquals(mWakelockController.getOnProximityNegativeMessages(), 0);
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+ () -> mWakelockController.isProximityNegativeAcquired());
- // Verify all suspend blockers were released
- verify(mDisplayPowerCallbacks, times(2))
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
.releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
}
@Test
- public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() {
+ public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() throws Exception {
// Acquire the suspend blocker
- mWakelockController.acquireProxDebounceSuspendBlocker();
-
- // Try to reacquire
- mWakelockController.acquireProxDebounceSuspendBlocker();
- assertTrue(mWakelockController.hasProximitySensorDebounced());
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+ () -> mWakelockController.hasProximitySensorDebounced());
// Verify acquire happened only once
verify(mDisplayPowerCallbacks, times(1))
.acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
// Release the suspend blocker
- assertTrue(mWakelockController.releaseProxDebounceSuspendBlocker());
-
- // Release again
- assertFalse(mWakelockController.releaseProxDebounceSuspendBlocker());
- assertFalse(mWakelockController.hasProximitySensorDebounced());
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+ () -> mWakelockController.hasProximitySensorDebounced());
// Verify suspend blocker was released only once
verify(mDisplayPowerCallbacks, times(1))
@@ -189,52 +164,126 @@ public final class WakelockControllerTest {
@Test
public void proximityPositiveRunnableWorksAsExpected() {
// Acquire the suspend blocker twice
- mWakelockController.acquireProxPositiveSuspendBlocker();
- mWakelockController.acquireProxPositiveSuspendBlocker();
+ assertTrue(mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE));
// Execute the runnable
Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
proximityPositiveRunnable.run();
// Validate one suspend blocker was released
- assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
+ assertFalse(mWakelockController.isProximityPositiveAcquired());
verify(mDisplayPowerCallbacks).onProximityPositive();
verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
mWakelockController.getSuspendBlockerProxPositiveId());
}
@Test
+ public void proximityPositiveRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
+ proximityPositiveRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityPositiveAcquired());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ @Test
public void proximityNegativeRunnableWorksAsExpected() {
// Acquire the suspend blocker twice
- mWakelockController.acquireProxNegativeSuspendBlocker();
- mWakelockController.acquireProxNegativeSuspendBlocker();
+ assertTrue(mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE));
// Execute the runnable
Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
proximityNegativeRunnable.run();
// Validate one suspend blocker was released
- assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
+ assertFalse(mWakelockController.isProximityNegativeAcquired());
verify(mDisplayPowerCallbacks).onProximityNegative();
verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
mWakelockController.getSuspendBlockerProxNegativeId());
}
@Test
+ public void proximityNegativeRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
+ proximityNegativeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityNegativeAcquired());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ @Test
public void onStateChangeRunnableWorksAsExpected() {
// Acquire the suspend blocker twice
- mWakelockController.acquireStateChangedSuspendBlocker();
+ assertTrue(mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_STATE_CHANGED));
// Execute the runnable
Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
stateChangeRunnable.run();
// Validate one suspend blocker was released
- assertEquals(mWakelockController.isOnStateChangedPending(), false);
+ assertFalse(mWakelockController.isOnStateChangedPending());
verify(mDisplayPowerCallbacks).onStateChanged();
verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
mWakelockController.getSuspendBlockerOnStateChangedId());
}
+ @Test
+ public void onStateChangeRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
+ stateChangeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isOnStateChangedPending());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ private void verifyWakelockAcquisitionAndReaquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ verifyWakelockAcquisition(wakelockId, isWakelockAcquiredCallable);
+ verifyWakelockReacquisition(wakelockId, isWakelockAcquiredCallable);
+ }
+
+ private void verifyWakelockReleaseAndRerelease(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ verifyWakelockRelease(wakelockId, isWakelockAcquiredCallable);
+ verifyWakelockRerelease(wakelockId, isWakelockAcquiredCallable);
+ }
+
+ private void verifyWakelockAcquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertTrue(mWakelockController.acquireWakelock(wakelockId));
+ assertTrue(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockReacquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertFalse(mWakelockController.acquireWakelock(wakelockId));
+ assertTrue(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockRelease(int wakelockId, Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertTrue(mWakelockController.releaseWakelock(wakelockId));
+ assertFalse(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockRerelease(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertFalse(mWakelockController.releaseWakelock(wakelockId));
+ assertFalse(isWakelockAcquiredCallable.call());
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index b354c7b3d1f3..f46877e5a8c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -16,30 +16,48 @@
package com.android.server.job;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_EJ;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_REGULAR;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
@@ -52,6 +70,12 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -64,10 +88,24 @@ public final class JobConcurrencyManagerTest {
private int mDefaultUserId;
private GracePeriodObserver mGracePeriodObserver;
private Context mContext;
+ private InjectorForTest mInjector;
+ private MockitoSession mMockingSession;
private Resources mResources;
private PendingJobQueue mPendingJobQueue;
private DeviceConfig.Properties.Builder mConfigBuilder;
+ @Mock
+ private IPackageManager mIPackageManager;
+
+ static class InjectorForTest extends JobConcurrencyManager.Injector {
+ @Override
+ JobServiceContext createJobServiceContext(JobSchedulerService service,
+ JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+ JobPackageTracker tracker, Looper looper) {
+ return mock(JobServiceContext.class);
+ }
+ }
+
@BeforeClass
public static void setUpOnce() {
LocalServices.addService(UserManagerInternal.class, mock(UserManagerInternal.class));
@@ -83,6 +121,11 @@ public final class JobConcurrencyManagerTest {
@Before
public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
mContext = mock(Context.class);
mResources = mock(Resources.class);
@@ -93,7 +136,9 @@ public final class JobConcurrencyManagerTest {
mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
mPendingJobQueue = new PendingJobQueue();
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
- mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService);
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+ mInjector = new InjectorForTest();
+ mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
mGracePeriodObserver = mock(GracePeriodObserver.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -106,6 +151,74 @@ public final class JobConcurrencyManagerTest {
@After
public void tearDown() throws Exception {
resetConfig();
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void testPrepareForAssignmentDetermination_noJobs() {
+ mPendingJobQueue.clear();
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ mJobConcurrencyManager
+ .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
+ assertEquals(0, preferredUidOnly.size());
+ assertEquals(0, stoppable.size());
+ }
+
+ @Test
+ public void testPrepareForAssignmentDetermination_onlyPendingJobs() {
+ final ArraySet<JobStatus> jobs = new ArraySet<>();
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+ mPendingJobQueue.add(job);
+ jobs.add(job);
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ mJobConcurrencyManager
+ .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
+ assertEquals(0, preferredUidOnly.size());
+ assertEquals(0, stoppable.size());
+ }
+
+ @Test
+ public void testDetermineAssignments_allRegular() throws Exception {
+ setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+ final ArraySet<JobStatus> jobs = new ArraySet<>();
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+ final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+ final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+ setPackageUid(sourcePkgName, uid);
+ final JobStatus job = createJob(uid, sourcePkgName);
+ mPendingJobQueue.add(job);
+ jobs.add(job);
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ mJobConcurrencyManager
+ .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ mJobConcurrencyManager
+ .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
+
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
+ for (int i = changed.size() - 1; i >= 0; --i) {
+ jobs.remove(changed.valueAt(i).newJob);
+ }
+ assertTrue("Some jobs weren't assigned", jobs.isEmpty());
}
@Test
@@ -403,16 +516,58 @@ public final class JobConcurrencyManagerTest {
}
private static JobStatus createJob(int uid) {
- return createJob(uid, 1);
+ return createJob(uid, 1, null);
+ }
+
+ private static JobStatus createJob(int uid, String sourcePackageName) {
+ return createJob(uid, 1, sourcePackageName);
}
private static JobStatus createJob(int uid, int jobId) {
+ return createJob(uid, jobId, null);
+ }
+
+ private static JobStatus createJob(int uid, int jobId, @Nullable String sourcePackageName) {
return JobStatus.createFromJobInfo(
new JobInfo.Builder(jobId, new ComponentName("foo", "bar")).build(), uid,
- null, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
+ sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
}
- private void setConcurrencyConfig(int total) throws Exception {
+ private static final class TypeConfig {
+ public final String workTypeString;
+ public final int min;
+ public final int max;
+
+ private TypeConfig(@JobConcurrencyManager.WorkType int workType, int min, int max) {
+ switch (workType) {
+ case WORK_TYPE_TOP:
+ workTypeString = "top";
+ break;
+ case WORK_TYPE_FGS:
+ workTypeString = "fgs";
+ break;
+ case WORK_TYPE_EJ:
+ workTypeString = "ej";
+ break;
+ case WORK_TYPE_BG:
+ workTypeString = "bg";
+ break;
+ case WORK_TYPE_BGUSER:
+ workTypeString = "bguser";
+ break;
+ case WORK_TYPE_BGUSER_IMPORTANT:
+ workTypeString = "bguser_important";
+ break;
+ case WORK_TYPE_NONE:
+ default:
+ throw new IllegalArgumentException("invalid work type: " + workType);
+ }
+ this.min = min;
+ this.max = max;
+ }
+ }
+
+ private void setConcurrencyConfig(int total, TypeConfig... typeConfigs) throws Exception {
// Set the values for all memory states so we don't have to worry about memory on the device
// during testing.
final String[] identifiers = {
@@ -422,10 +577,23 @@ public final class JobConcurrencyManagerTest {
for (String identifier : identifiers) {
mConfigBuilder
.setInt(WorkTypeConfig.KEY_PREFIX_MAX_TOTAL + identifier, total);
+ for (TypeConfig config : typeConfigs) {
+ mConfigBuilder.setInt(
+ WorkTypeConfig.KEY_PREFIX_MAX + config.workTypeString + "_" + identifier,
+ config.max);
+ mConfigBuilder.setInt(
+ WorkTypeConfig.KEY_PREFIX_MIN + config.workTypeString + "_" + identifier,
+ config.min);
+ }
}
updateDeviceConfig();
}
+ private void setPackageUid(final String pkgName, final int uid) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
private void updateDeviceConfig() throws Exception {
DeviceConfig.setProperties(mConfigBuilder.build());
mJobConcurrencyManager.updateConfigLocked();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 1d08a80d3e40..c58104a50613 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -34,6 +34,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NO
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
+import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
import static org.junit.Assert.assertArrayEquals;
@@ -143,6 +144,7 @@ public class FlexibilityControllerTest {
mPrefetchController);
mFcConfig = mFlexibilityController.getFcConfig();
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
}
@@ -233,21 +235,22 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_PercentsToDropConstraints() {
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+ JobInfo.Builder jb = createJob(0)
+ .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
- assertEquals(150L,
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
assertArrayEquals(
mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
new int[] {10, 20, 30, 40});
- assertEquals(110L,
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.adjustNumRequiredFlexibleConstraints(-1);
- assertEquals(120L,
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.adjustNumRequiredFlexibleConstraints(-1);
- assertEquals(130L,
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
}
@@ -274,24 +277,27 @@ public class FlexibilityControllerTest {
long nextTimeToDropNumConstraints;
// no delay, deadline
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
JobStatus js = createJobStatus("time", jb);
assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
- assertEquals(1000 + FROZEN_TIME, js.getLatestRunTimeElapsed());
+ assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
assertEquals(FROZEN_TIME, js.enqueueTime);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(600L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+ nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(700L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+ nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(800L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+ nextTimeToDropNumConstraints);
// delay, no deadline
jb = createJob(0).setMinimumLatency(800000L);
@@ -326,20 +332,26 @@ public class FlexibilityControllerTest {
assertEquals(181440100L, nextTimeToDropNumConstraints);
// delay, deadline
- jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
+ jb = createJob(0)
+ .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
+ .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
js = createJobStatus("time", jb);
+ final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(700L, nextTimeToDropNumConstraints);
+ assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+ nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(800L, nextTimeToDropNumConstraints);
+ assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+ nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(900L, nextTimeToDropNumConstraints);
+ assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+ nextTimeToDropNumConstraints);
}
@Test
@@ -734,10 +746,9 @@ public class FlexibilityControllerTest {
@Test
public void testResetJobNumDroppedConstraints() {
- JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
+ JobInfo.Builder jb = createJob(22);
JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
- js.adjustNumRequiredFlexibleConstraints(3);
- long nowElapsed;
+ long nowElapsed = FROZEN_TIME;
mFlexibilityController.mFlexibilityTracker.add(js);
@@ -746,8 +757,7 @@ public class FlexibilityControllerTest {
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
-
- nowElapsed = 155L;
+ nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -766,7 +776,7 @@ public class FlexibilityControllerTest {
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
- nowElapsed = 140L;
+ nowElapsed = FROZEN_TIME;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -777,7 +787,7 @@ public class FlexibilityControllerTest {
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
- nowElapsed = 175L;
+ nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -786,7 +796,7 @@ public class FlexibilityControllerTest {
assertEquals(0, js.getNumRequiredFlexibleConstraints());
assertEquals(3, js.getNumDroppedFlexibleConstraints());
- nowElapsed = 165L;
+ nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index cc57b9f913e3..dc7bcd625ee3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -38,6 +38,7 @@ import android.os.UserManager
import android.os.incremental.IncrementalManager
import android.provider.DeviceConfig
import android.util.ArrayMap
+import android.util.ArraySet
import android.util.DisplayMetrics
import android.util.EventLog
import android.view.Display
@@ -294,6 +295,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
+ whenever(mocks.systemConfig.defaultVrComponents).thenReturn(ArraySet())
+ whenever(mocks.systemConfig.hiddenApiWhitelistedApps).thenReturn(ArraySet())
wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true)
wheneverStatic { Environment.getPackageCacheDirectory() }.thenReturn(packageCacheDirectory)
wheneverStatic { SystemProperties.digestOf("ro.build.fingerprint") }.thenReturn("cacheName")
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index bccd8a0b14b4..9ae892286e55 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,6 +18,7 @@ package com.android.server.backup;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -61,6 +62,7 @@ import java.util.function.IntConsumer;
public class UserBackupManagerServiceTest {
private static final String TEST_PACKAGE = "package1";
private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+ private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
@Mock Context mContext;
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@@ -179,6 +181,7 @@ public class UserBackupManagerServiceTest {
mService.agentDisconnected("com.android.foo");
+ mService.waitForAsyncOperation();
verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class));
verify(mOperationStorage).cancelOperation(eq(456), eq(true), any());
verify(mOperationStorage).cancelOperation(eq(789), eq(true), any());
@@ -207,6 +210,8 @@ public class UserBackupManagerServiceTest {
boolean isEnabledStatePersisted = false;
boolean shouldUseNewBackupEligibilityRules = false;
+ private volatile Thread mWorkerThread = null;
+
TestBackupService(Context context, PackageManager packageManager,
LifecycleOperationStorage operationStorage) {
super(context, packageManager, operationStorage);
@@ -229,5 +234,23 @@ public class UserBackupManagerServiceTest {
boolean shouldUseNewBackupEligibilityRules() {
return shouldUseNewBackupEligibilityRules;
}
+
+ @Override
+ Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
+ mWorkerThread = super.getThreadForAsyncOperation(operationName, operation);
+ return mWorkerThread;
+ }
+
+ private void waitForAsyncOperation() {
+ if (mWorkerThread == null) {
+ return;
+ }
+
+ try {
+ mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Failed waiting for worker thread to complete: " + e.getMessage());
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 10f0a5cbbc40..68c9ce4a9f86 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -50,6 +51,9 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -93,7 +97,7 @@ public class ALSProbeTest {
mSensorEventListenerCaptor.getValue().onSensorChanged(
new SensorEvent(mLightSensor, 1, 2, new float[]{value}));
- assertThat(mProbe.getCurrentLux()).isEqualTo(value);
+ assertThat(mProbe.getMostRecentLux()).isEqualTo(value);
}
@Test
@@ -121,13 +125,17 @@ public class ALSProbeTest {
mProbe.destroy();
mProbe.enable();
+ AtomicInteger lux = new AtomicInteger(10);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
+ assertThat(lux.get()).isLessThan(0);
}
@Test
public void testDisabledReportsNegativeValue() {
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
mProbe.enable();
verify(mSensorManager).registerListener(
@@ -136,7 +144,7 @@ public class ALSProbeTest {
new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
mProbe.disable();
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
}
@Test
@@ -150,7 +158,7 @@ public class ALSProbeTest {
verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
verifyNoMoreInteractions(mSensorManager);
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
}
@Test
@@ -166,7 +174,148 @@ public class ALSProbeTest {
verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
verifyNoMoreInteractions(mSensorManager);
- assertThat(mProbe.getCurrentLux()).isLessThan(0f);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
+ }
+
+ @Test
+ public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
+ testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
+ }
+
+ @Test
+ public void testNextLuxWhenAlreadyEnabledAndAvailable() {
+ testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */);
+ }
+
+ private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) {
+ final List<Integer> values = List.of(1, 2, 3, 4, 6);
+ mProbe.enable();
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ if (dataIsAvailable) {
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+ }
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ if (!dataIsAvailable) {
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+ }
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{200f}));
+
+ // should remain enabled
+ assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0));
+ verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+
+ final int anotherValue = 12;
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{12}));
+ assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue);
+ }
+
+ @Test
+ public void testNextLuxWhenNotEnabled() {
+ testNextLuxWhenNotEnabled(false /* enableWhileWaiting */);
+ }
+
+ @Test
+ public void testNextLuxWhenNotEnabledButEnabledLater() {
+ testNextLuxWhenNotEnabled(true /* enableWhileWaiting */);
+ }
+
+ private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) {
+ final List<Integer> values = List.of(1, 2, 3, 4, 6);
+ mProbe.disable();
+
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ if (enableWhileWaiting) {
+ mProbe.enable();
+ }
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ for (int v : values) {
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
+ }
+
+ // should restore the disabled state
+ assertThat(lux.get()).isEqualTo(values.get(0));
+ verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener(
+ any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
+ public void testNextLuxIsNotCanceledByDisableOrDestroy() {
+ final int value = 7;
+ AtomicInteger lux = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ mProbe.destroy();
+ mProbe.disable();
+
+ assertThat(lux.get()).isEqualTo(-1);
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
+
+ assertThat(lux.get()).isEqualTo(value);
+
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1}));
+
+ // should remain destroyed
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(value);
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
+ public void testMultipleNextConsumers() {
+ final int value = 7;
+ AtomicInteger lux = new AtomicInteger(-1);
+ AtomicInteger lux2 = new AtomicInteger(-1);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
+
+ assertThat(lux.get()).isEqualTo(value);
+ assertThat(lux2.get()).isEqualTo(value);
+ }
+
+ @Test
+ public void testNoNextLuxWhenDestroyed() {
+ mProbe.destroy();
+
+ AtomicInteger lux = new AtomicInteger(-20);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ assertThat(lux.get()).isEqualTo(-1);
+ verify(mSensorManager, never()).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+ verifyNoMoreInteractions(mSensorManager);
}
private void moveTimeBy(long millis) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 60dc2eb6081d..88a9646cac8a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -121,7 +121,7 @@ public class BiometricLoggerTest {
verify(mSink).authenticate(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
anyLong(), anyInt(), eq(requireConfirmation),
- eq(targetUserId), anyFloat());
+ eq(targetUserId), any());
}
@Test
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 dea4d4fb7c64..a5c181d53286 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
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -215,7 +216,7 @@ public class FingerprintAuthenticationClientTest {
@Test
public void luxProbeWhenAwake() throws RemoteException {
- when(mBiometricContext.isAwake()).thenReturn(false, true, false);
+ when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
@@ -228,15 +229,38 @@ public class FingerprintAuthenticationClientTest {
verify(mLuxProbe, never()).enable();
reset(mLuxProbe);
+ when(mBiometricContext.isAwake()).thenReturn(true);
+
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).enable();
verify(mLuxProbe, never()).disable();
+ when(mBiometricContext.isAwake()).thenReturn(false);
+
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).disable();
}
@Test
+ public void luxProbeEnabledOnStartWhenWake() throws RemoteException {
+ luxProbeEnabledOnStart(true /* isAwake */);
+ }
+
+ @Test
+ public void luxProbeNotEnabledOnStartWhenNotWake() throws RemoteException {
+ luxProbeEnabledOnStart(false /* isAwake */);
+ }
+
+ private void luxProbeEnabledOnStart(boolean isAwake) throws RemoteException {
+ when(mBiometricContext.isAwake()).thenReturn(isAwake);
+ when(mBiometricContext.isAod()).thenReturn(false);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ verify(mLuxProbe, isAwake ? times(1) : never()).enable();
+ }
+
+ @Test
public void luxProbeDisabledOnAod() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 1e67c120b6e0..3f4bec653091 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -49,6 +50,8 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;
+import libcore.util.HexEncoding;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,6 +87,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
long protectorId = manager.createLskfBasedProtector(mGateKeeperService,
LockscreenCredential.createNone(), sp, USER_ID);
assertFalse(lskfGatekeeperHandleExists(USER_ID));
+ assertFalse(manager.hasPasswordData(protectorId, USER_ID));
+ assertFalse(manager.hasPasswordMetrics(protectorId, USER_ID));
AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
protectorId, LockscreenCredential.createNone(), USER_ID, null);
@@ -103,6 +108,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
USER_ID);
assertTrue(lskfGatekeeperHandleExists(USER_ID));
+ assertTrue(manager.hasPasswordData(protectorId, USER_ID));
+ assertTrue(manager.hasPasswordMetrics(protectorId, USER_ID));
AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
protectorId, password, USER_ID, null);
@@ -452,19 +459,46 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
}
+ // Tests stretching of a nonempty LSKF.
@Test
- public void testPasswordData_scryptParams() {
- // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used.
- PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE);
- assertEquals(1, data.scryptLogN);
- assertEquals(0, data.scryptLogR);
- assertEquals(0, data.scryptLogP);
+ public void testStretchLskf_enabled() {
+ byte[] actual = mSpManager.stretchLskf(newPin("12345"), createTestPasswordData());
+ String expected = "467986710DE8F0D4F4A3668DFF58C9B7E5DB96A79B7CCF415BBD4D7767F8CFFA";
+ assertEquals(expected, HexEncoding.encodeToString(actual));
+ }
- // Any other credential type should result in the real scrypt params being used.
- data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD);
- assertTrue(data.scryptLogN > 1);
- assertTrue(data.scryptLogR > 0);
- assertTrue(data.scryptLogP > 0);
+ // Tests the case where stretching is disabled for an empty LSKF.
+ @Test
+ public void testStretchLskf_disabled() {
+ byte[] actual = mSpManager.stretchLskf(nonePassword(), null);
+ // "default-password", zero padded
+ String expected = "64656661756C742D70617373776F726400000000000000000000000000000000";
+ assertEquals(expected, HexEncoding.encodeToString(actual));
+ }
+
+ // Tests the legacy case where stretching is enabled for an empty LSKF.
+ @Test
+ public void testStretchLskf_emptyButEnabled() {
+ byte[] actual = mSpManager.stretchLskf(nonePassword(), createTestPasswordData());
+ String expected = "9E6DDCC1EC388BB1E1CD54097AF924CA80BCB90993196FA8F6122FF58EB333DE";
+ assertEquals(expected, HexEncoding.encodeToString(actual));
+ }
+
+ // Tests the forbidden case where stretching is disabled for a nonempty LSKF.
+ @Test
+ public void testStretchLskf_nonEmptyButDisabled() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mSpManager.stretchLskf(newPin("12345"), null));
+ }
+
+ private PasswordData createTestPasswordData() {
+ PasswordData data = new PasswordData();
+ // For the unit test, the scrypt parameters have to be constant; the salt can't be random.
+ data.scryptLogN = 11;
+ data.scryptLogR = 3;
+ data.scryptLogP = 1;
+ data.salt = "abcdefghijklmnop".getBytes();
+ return data;
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2f23e7fac75f..8a18912b8b68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2319,6 +2319,22 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie));
}
+ @Test
+ public void testOrientationForScreenOrientationBehind() {
+ final Task task = createTask(mDisplayContent);
+ // Activity below
+ new ActivityBuilder(mAtm)
+ .setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ final ActivityRecord activityTop = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ final int topOrientation = activityTop.getRequestedConfigurationOrientation();
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, topOrientation);
+ }
+
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
boolean shouldUpdate, boolean activityChange) {
reset(activity.app);
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 79ab009d3b92..5179babbd31d 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -27,7 +27,6 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.SystemProperties;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import java.io.PrintWriter;
@@ -195,57 +194,20 @@ public final class TelephonyUtils {
}
/**
- * Convert display name source to string.
+ * Convert mobile data policy to string.
*
- * @param source The display name source.
- * @return The display name source in string format.
+ * @param mobileDataPolicy The mobile data policy.
+ * @return The mobile data policy in string format.
*/
- @NonNull
- public static String displayNameSourceToString(
- @SubscriptionManager.SimDisplayNameSource int source) {
- switch (source) {
- case SubscriptionManager.NAME_SOURCE_UNKNOWN: return "UNKNOWN";
- case SubscriptionManager.NAME_SOURCE_CARRIER_ID: return "CARRIER_ID";
- case SubscriptionManager.NAME_SOURCE_SIM_SPN: return "SIM_SPN";
- case SubscriptionManager.NAME_SOURCE_USER_INPUT: return "USER_INPUT";
- case SubscriptionManager.NAME_SOURCE_CARRIER: return "CARRIER";
- case SubscriptionManager.NAME_SOURCE_SIM_PNN: return "SIM_PNN";
+ public static @NonNull String mobileDataPolicyToString(
+ @TelephonyManager.MobileDataPolicy int mobileDataPolicy) {
+ switch (mobileDataPolicy) {
+ case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
+ return "DATA_ON_NON_DEFAULT_DURING_VOICE_CALL";
+ case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
+ return "MMS_ALWAYS_ALLOWED";
default:
- return "UNKNOWN(" + source + ")";
- }
- }
-
- /**
- * Convert subscription type to string.
- *
- * @param type The subscription type.
- * @return The subscription type in string format.
- */
- @NonNull
- public static String subscriptionTypeToString(@SubscriptionManager.SubscriptionType int type) {
- switch (type) {
- case SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM: return "LOCAL_SIM";
- case SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM: return "REMOTE_SIM";
- default:
- return "UNKNOWN(" + type + ")";
- }
- }
-
- /**
- * Convert usage setting to string.
- *
- * @param usageSetting Usage setting.
- * @return The usage setting in string format.
- */
- @NonNull
- public static String usageSettingToString(@SubscriptionManager.UsageSetting int usageSetting) {
- switch (usageSetting) {
- case SubscriptionManager.USAGE_SETTING_UNKNOWN: return "UNKNOWN";
- case SubscriptionManager.USAGE_SETTING_DEFAULT: return "DEFAULT";
- case SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC: return "VOICE_CENTRIC";
- case SubscriptionManager.USAGE_SETTING_DATA_CENTRIC: return "DATA_CENTRIC";
- default:
- return "UNKNOWN(" + usageSetting + ")";
+ return "UNKNOWN(" + mobileDataPolicy + ")";
}
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0622612bb064..f43f0a53650b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4840,11 +4840,32 @@ public class CarrierConfigManager {
* The max acceptable value of this config is 24 hours.
*
* @hide
+ * @deprecated Use {@link #KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG} instead.
*/
+ @Deprecated
public static final String KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG =
"data_switch_validation_min_gap_long";
/**
+ * Data switch validation minimal interval, in milliseconds.
+ *
+ * If a connection to the default (Internet) PDN for the current subscription is validated on
+ * a given operator within a given tracking area, re-validations to that matching operator will
+ * be skipped if they would occur within the specified interval. Instead, the connection will
+ * automatically considered validated.
+ *
+ * If the network was validated within the interval but the latest validation result was false,
+ * the validation will not be skipped. If not set or set to 0, validation will not be skipped.
+ *
+ * The valid range of value is between 0 millisecond and 24 hours, inclusive in both sides. The
+ * default value is 24 hours.
+ *
+ * @see android.net.NetworkCapabilities#NET_CAPABILITY_VALIDATED
+ */
+ public static final String KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG =
+ KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
+
+ /**
* A boolean property indicating whether this subscription should be managed as an opportunistic
* subscription.
*
@@ -9360,7 +9381,8 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_GBA_UA_TLS_CIPHER_SUITE_INT, TlsParams.TLS_NULL_WITH_NULL_NULL);
sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false);
- sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, TimeUnit.DAYS.toMillis(1));
+ sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG,
+ TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
new String[0]);
sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index e0145e661660..c2b65f86ff02 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1092,6 +1092,11 @@ public class SmsMessage {
if (!TextUtils.isEmpty(simOperator)) {
for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (currentConfig == null) {
+ Rlog.w("SmsMessage", "hasEmsSupport currentConfig is null");
+ continue;
+ }
+
if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
(TextUtils.isEmpty(currentConfig.mGid1) ||
(!TextUtils.isEmpty(currentConfig.mGid1) &&
@@ -1155,18 +1160,21 @@ public class SmsMessage {
private static boolean mIsNoEmsSupportConfigListLoaded = false;
private static boolean isNoEmsSupportConfigListExisted() {
- if (!mIsNoEmsSupportConfigListLoaded) {
- Resources r = Resources.getSystem();
- if (r != null) {
- String[] listArray = r.getStringArray(
- com.android.internal.R.array.no_ems_support_sim_operators);
- if ((listArray != null) && (listArray.length > 0)) {
- mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
- for (int i=0; i<listArray.length; i++) {
- mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
+ synchronized (SmsMessage.class) {
+ if (!mIsNoEmsSupportConfigListLoaded) {
+ Resources r = Resources.getSystem();
+ if (r != null) {
+ String[] listArray = r.getStringArray(
+ com.android.internal.R.array.no_ems_support_sim_operators);
+ if ((listArray != null) && (listArray.length > 0)) {
+ mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+ for (int i = 0; i < listArray.length; i++) {
+ mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(
+ listArray[i].split(";"));
+ }
}
+ mIsNoEmsSupportConfigListLoaded = true;
}
- mIsNoEmsSupportConfigListLoaded = true;
}
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 0d3c80fd8887..e055f637b72c 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -974,7 +974,7 @@ public class SubscriptionInfo implements Parcelable {
+ " groupOwner=" + mGroupOwner
+ " isGroupDisabled=" + mIsGroupDisabled
+ " displayNameSource="
- + TelephonyUtils.displayNameSourceToString(mDisplayNameSource)
+ + SubscriptionManager.displayNameSourceToString(mDisplayNameSource)
+ " iconTint=" + mIconTint
+ " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+ " dataRoaming=" + mDataRoaming
@@ -988,9 +988,9 @@ public class SubscriptionInfo implements Parcelable {
+ " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
+ " countryIso=" + mCountryIso
+ " profileClass=" + mProfileClass
- + " mType=" + TelephonyUtils.subscriptionTypeToString(mType)
+ + " mType=" + SubscriptionManager.subscriptionTypeToString(mType)
+ " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
- + " usageSetting=" + TelephonyUtils.usageSettingToString(mUsageSetting)
+ + " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting)
+ "]";
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6189b49bcf79..e6c6b62497a8 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4092,4 +4092,65 @@ public class SubscriptionManager {
(iSub)-> iSub.setUsageSetting(
usageSetting, subscriptionId, mContext.getOpPackageName()));
}
+
+ /**
+ * Convert display name source to string.
+ *
+ * @param source The display name source.
+ * @return The display name source in string format.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String displayNameSourceToString(
+ @SubscriptionManager.SimDisplayNameSource int source) {
+ switch (source) {
+ case SubscriptionManager.NAME_SOURCE_UNKNOWN: return "UNKNOWN";
+ case SubscriptionManager.NAME_SOURCE_CARRIER_ID: return "CARRIER_ID";
+ case SubscriptionManager.NAME_SOURCE_SIM_SPN: return "SIM_SPN";
+ case SubscriptionManager.NAME_SOURCE_USER_INPUT: return "USER_INPUT";
+ case SubscriptionManager.NAME_SOURCE_CARRIER: return "CARRIER";
+ case SubscriptionManager.NAME_SOURCE_SIM_PNN: return "SIM_PNN";
+ default:
+ return "UNKNOWN(" + source + ")";
+ }
+ }
+
+ /**
+ * Convert subscription type to string.
+ *
+ * @param type The subscription type.
+ * @return The subscription type in string format.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String subscriptionTypeToString(@SubscriptionManager.SubscriptionType int type) {
+ switch (type) {
+ case SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM: return "LOCAL_SIM";
+ case SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM: return "REMOTE_SIM";
+ default:
+ return "UNKNOWN(" + type + ")";
+ }
+ }
+
+ /**
+ * Convert usage setting to string.
+ *
+ * @param usageSetting Usage setting.
+ * @return The usage setting in string format.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String usageSettingToString(@SubscriptionManager.UsageSetting int usageSetting) {
+ switch (usageSetting) {
+ case SubscriptionManager.USAGE_SETTING_UNKNOWN: return "UNKNOWN";
+ case SubscriptionManager.USAGE_SETTING_DEFAULT: return "DEFAULT";
+ case SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC: return "VOICE_CENTRIC";
+ case SubscriptionManager.USAGE_SETTING_DATA_CENTRIC: return "DATA_CENTRIC";
+ default:
+ return "UNKNOWN(" + usageSetting + ")";
+ }
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8818ac2f6ee0..f3d48a849b0f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9113,7 +9113,8 @@ public class TelephonyManager {
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges})
- * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION} if includeLocationData is
+ * set to {@link #INCLUDE_LOCATION_DATA_FINE}.
*
* If the system-wide location switch is off, apps may still call this API, with the
* following constraints:
@@ -9127,7 +9128,10 @@ public class TelephonyManager {
* </ol>
*
* @param includeLocationData Specifies if the caller would like to receive
- * location related information.
+ * location related information. If this parameter is set to
+ * {@link #INCLUDE_LOCATION_DATA_FINE} then the application will be checked for
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission and available
+ * location related information received during network scan will be sent to the caller.
* @param request Contains all the RAT with bands/channels that need to be scanned.
* @param executor The executor through which the callback should be invoked. Since the scan
* request may trigger multiple callbacks and they must be invoked in the same order as
@@ -9138,8 +9142,7 @@ public class TelephonyManager {
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
- android.Manifest.permission.MODIFY_PHONE_STATE,
- Manifest.permission.ACCESS_FINE_LOCATION
+ android.Manifest.permission.MODIFY_PHONE_STATE
})
public @Nullable NetworkScan requestNetworkScan(
@IncludeLocationData int includeLocationData,
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index e658c2e45ecd..caee4e2ca277 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -72,6 +72,7 @@ public class ImsMmTelManager implements RegistrationManager {
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "WIFI_MODE_", value = {
+ WIFI_MODE_UNKNOWN,
WIFI_MODE_WIFI_ONLY,
WIFI_MODE_CELLULAR_PREFERRED,
WIFI_MODE_WIFI_PREFERRED
@@ -79,6 +80,12 @@ public class ImsMmTelManager implements RegistrationManager {
public @interface WiFiCallingMode {}
/**
+ * Wifi calling mode is unknown. This is for initialization only.
+ * @hide
+ */
+ public static final int WIFI_MODE_UNKNOWN = -1;
+
+ /**
* Register for IMS over IWLAN if WiFi signal quality is high enough. Do not hand over to LTE
* registration if signal quality degrades.
*/
@@ -1573,4 +1580,24 @@ public class ImsMmTelManager implements RegistrationManager {
.get());
return binder;
}
+
+ /**
+ * Convert Wi-Fi calling mode to string.
+ *
+ * @param mode Wi-Fi calling mode.
+ * @return The Wi-Fi calling mode in string format.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String wifiCallingModeToString(@ImsMmTelManager.WiFiCallingMode int mode) {
+ switch (mode) {
+ case ImsMmTelManager.WIFI_MODE_UNKNOWN: return "UNKNOWN";
+ case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: return "WIFI_ONLY";
+ case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: return "CELLULAR_PREFERRED";
+ case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: return "WIFI_PREFERRED";
+ default:
+ return "UNKNOWN(" + mode + ")";
+ }
+ }
}
diff --git a/tests/FlickerTests/res/anim/show_2000ms.xml b/tests/FlickerTests/res/anim/show_hide_show_3000ms.xml
index 76e375f12e31..7b3f07e3a2f5 100644
--- a/tests/FlickerTests/res/anim/show_2000ms.xml
+++ b/tests/FlickerTests/res/anim/show_hide_show_3000ms.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -15,7 +14,18 @@
~ limitations under the License.
-->
-<translate xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="2000"
- android:fromXDelta="0"
- android:toXDelta="0" /> \ No newline at end of file
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fillAfter="true">
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:duration="1000" />
+
+ <alpha
+ android:startOffset="2000"
+ android:fromAlpha="1.0"
+ android:toAlpha="1.0"
+ android:duration="1000" />
+</set> \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index cb61e35eaedc..0837c0046183 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -24,6 +24,7 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -47,96 +48,115 @@ class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) :
}
@FlakyTest
+ @Test
override fun appLayerReplacesLauncher() {
super.appLayerReplacesLauncher()
}
@FlakyTest
+ @Test
override fun appLayerBecomesVisible() {
super.appLayerBecomesVisible()
}
@FlakyTest
+ @Test
override fun appWindowBecomesTopWindow() {
super.appWindowBecomesTopWindow()
}
@FlakyTest
+ @Test
override fun appWindowBecomesVisible() {
super.appWindowBecomesVisible()
}
@FlakyTest
+ @Test
override fun appWindowIsTopWindowAtEnd() {
super.appWindowIsTopWindowAtEnd()
}
@FlakyTest
+ @Test
override fun appWindowReplacesLauncherAsTopWindow() {
super.appWindowReplacesLauncherAsTopWindow()
}
@FlakyTest
+ @Test
override fun entireScreenCovered() {
super.entireScreenCovered()
}
@FlakyTest
+ @Test
override fun navBarLayerIsVisibleAtStartAndEnd() {
super.navBarLayerIsVisibleAtStartAndEnd()
}
@FlakyTest
+ @Test
override fun navBarLayerPositionAtStartAndEnd() {
super.navBarLayerPositionAtStartAndEnd()
}
@FlakyTest
+ @Test
override fun navBarWindowIsAlwaysVisible() {
super.navBarWindowIsAlwaysVisible()
}
@FlakyTest
+ @Test
override fun statusBarLayerIsVisibleAtStartAndEnd() {
super.statusBarLayerIsVisibleAtStartAndEnd()
}
@FlakyTest
+ @Test
override fun statusBarLayerPositionAtStartAndEnd() {
super.statusBarLayerPositionAtStartAndEnd()
}
@FlakyTest
+ @Test
override fun statusBarWindowIsAlwaysVisible() {
super.statusBarWindowIsAlwaysVisible()
}
@FlakyTest
+ @Test
override fun taskBarLayerIsVisibleAtStartAndEnd() {
super.taskBarLayerIsVisibleAtStartAndEnd()
}
@FlakyTest
+ @Test
override fun taskBarWindowIsAlwaysVisible() {
super.taskBarWindowIsAlwaysVisible()
}
@FlakyTest
+ @Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
@FlakyTest
+ @Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
}
@FlakyTest
+ @Test
override fun focusChanges() {
super.focusChanges()
}
@FlakyTest
+ @Test
override fun appWindowAsTopWindowAtEnd() {
super.appWindowAsTopWindowAtEnd()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index bc2fe469a010..e1fd5a769255 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -30,13 +30,11 @@ import com.android.server.wm.flicker.R
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +48,7 @@ import org.junit.runners.Parameterized
*
* Actions:
* ```
- * Launches SimpleActivity with alpha_2000ms animation
+ * Launches SimpleActivity with a special animation.
* ```
*/
@RequiresDevice
@@ -89,21 +87,31 @@ class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
@Presubmit
@Test
fun testSimpleActivityIsShownDirectly() {
- Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.assertLayers {
+ // Before the app launches, only the launcher is visible.
isVisible(ComponentNameMatcher.LAUNCHER)
- .isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
- .isInvisible(testApp)
- .then()
- // The custom animation should block the entire launcher from the very beginning
- .isInvisible(ComponentNameMatcher.LAUNCHER)
+ .isInvisible(testApp)
+ .then()
+ // Animation starts, but the app may not be drawn yet which means the Splash
+ // may be visible.
+ .isInvisible(testApp, isOptional = true)
+ .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .then()
+ // App shows up with the custom animation starting at alpha=1.
+ .isVisible(testApp)
+ .then()
+ // App custom animation continues to alpha=0 (invisible).
+ .isInvisible(testApp)
+ .then()
+ // App custom animation ends with it being visible.
+ .isVisible(testApp)
}
}
private fun createCustomTaskAnimation(): Bundle {
return android.app.ActivityOptions.makeCustomTaskAnimation(
instrumentation.context,
- R.anim.show_2000ms,
+ R.anim.show_hide_show_3000ms,
0,
Handler.getMain(),
null,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 06486ca85292..08624eed27a5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.app.WallpaperManager
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -26,8 +27,8 @@ 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.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -56,7 +57,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
+ private val testApp = NewTasksAppHelper(instrumentation)
+ private val simpleApp = SimpleAppHelper(instrumentation)
private val wallpaper by lazy {
getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
}
@@ -76,7 +78,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
* Checks that the [wallpaper] window is never visible when performing task transitions. A solid
* color background should be shown instead.
*/
- @Postsubmit
+ @FlakyTest(bugId = 253617416)
@Test
fun wallpaperWindowIsNeverVisible() {
testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
@@ -86,7 +88,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
* Checks that the [wallpaper] layer is never visible when performing task transitions. A solid
* color background should be shown instead.
*/
- @Postsubmit
+ @FlakyTest(bugId = 253617416)
@Test
fun wallpaperLayerIsNeverVisible() {
testSpec.assertLayers {
@@ -116,7 +118,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
}
/** Checks that a color background is visible while the task transition is occurring. */
- @Postsubmit
+ @FlakyTest(bugId = 240570652)
@Test
fun colorLayerIsVisibleDuringTransition() {
val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
@@ -124,7 +126,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
testSpec.assertLayers {
this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
- it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+ it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
}
.isInvisible(bgColorLayer)
.then()
@@ -133,7 +135,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
.then()
// Fully transitioned to simple SIMPLE_ACTIVITY
.invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
- it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds)
+ it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds)
}
.isInvisible(bgColorLayer)
.then()
@@ -142,7 +144,7 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
.then()
// Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
- it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+ it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
}
.isInvisible(bgColorLayer)
}
@@ -156,15 +158,15 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
@Test
fun newTaskOpensOnTopAndThenCloses() {
testSpec.assertWm {
- this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ this.isAppWindowOnTop(testApp.componentMatcher)
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
.then()
- .isAppWindowOnTop(SIMPLE_ACTIVITY)
+ .isAppWindowOnTop(simpleApp.componentMatcher)
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
.then()
- .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ .isAppWindowOnTop(testApp.componentMatcher)
}
}
@@ -225,10 +227,6 @@ class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
- private val LAUNCH_NEW_TASK_ACTIVITY =
- ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
- private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
-
private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index c84c2bcf19c6..92ea029bf469 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -18,9 +18,8 @@ package com.android.inputmethod.stresstest;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
@@ -30,19 +29,27 @@ import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
+import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests to verify the "auto show" behavior in {@code InputMethodManagerService} when the window
+ * gaining the focus to start the input.
+ */
@RootPermissionTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public final class AutoShowTest {
@Rule
@@ -52,18 +59,95 @@ public final class AutoShowTest {
public ScreenCaptureRule mScreenCaptureRule =
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+ private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
+ new int[] {
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+ };
+
+ private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+ new int[] {
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+ };
+
+ // TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
+ @Parameterized.Parameters(
+ name =
+ "softInputVisibility={0}, softInputAdjustment={1},"
+ + " softInputModeIsForwardNavigation={2}")
+ public static List<Object[]> softInputModeConfigs() {
+ ArrayList<Object[]> params = new ArrayList<>();
+ for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
+ for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+ params.add(new Object[] {softInputVisibility, softInputAdjust, true});
+ params.add(new Object[] {softInputVisibility, softInputAdjust, false});
+ }
+ }
+ return params;
+ }
+
+ private static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+
+ private final int mSoftInputVisibility;
+ private final int mSoftInputAdjustment;
+ private final boolean mSoftInputIsForwardNavigation;
+
+ public AutoShowTest(
+ int softInputVisibility,
+ int softInputAdjustment,
+ boolean softInputIsForwardNavigation) {
+ mSoftInputVisibility = softInputVisibility;
+ mSoftInputAdjustment = softInputAdjustment;
+ mSoftInputIsForwardNavigation = softInputIsForwardNavigation;
+ }
+
@Test
public void autoShow() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ int flags = mSoftInputVisibility | mSoftInputAdjustment;
+ if (mSoftInputIsForwardNavigation) {
+ flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ }
+
+ Intent intent =
+ new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(SOFT_INPUT_FLAGS, flags);
TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
EditText editText = activity.getEditText();
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
- waitOnMainUntilImeIsShown(editText);
+
+ if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
+ || mSoftInputVisibility
+ == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
+ // IME will be auto-shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
+ waitOnMainUntilImeIsShown(editText);
+ } else if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
+ || mSoftInputVisibility
+ == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+ // IME will be not be shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
+ verifyImeIsAlwaysHidden(editText);
+ } else {
+ // The current system behavior will choose to show IME automatically when navigating
+ // forward to an app that has no visibility state specified (i.e.
+ // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
+ if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ && mSoftInputAdjustment == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+ && mSoftInputIsForwardNavigation) {
+ waitOnMainUntilImeIsShown(editText);
+ }
+ }
}
public static class TestActivity extends Activity {
@@ -72,16 +156,15 @@ public final class AutoShowTest {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // IME will be auto-shown if the following conditions are met:
- // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED.
- // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE.
- getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE);
+ int flags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
+ getWindow().setSoftInputMode(flags);
LinearLayout rootView = new LinearLayout(this);
rootView.setOrientation(LinearLayout.VERTICAL);
mEditText = new EditText(this);
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
setContentView(rootView);
- // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true).
+ // Ensure the focused view is a text editor (View#onCheckIsTextEditor() returns true) to
+ // automatically display a soft input window.
mEditText.requestFocus();
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index ba2ba3c75bc2..b6d462c6203d 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -25,6 +25,8 @@ import android.view.WindowInsets;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.ThrowingRunnable;
+
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -33,6 +35,7 @@ import java.util.concurrent.atomic.AtomicReference;
public final class ImeStressTestUtil {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long VERIFY_DURATION = TimeUnit.SECONDS.toMillis(2);
private ImeStressTestUtil() {
}
@@ -77,4 +80,41 @@ public final class ImeStressTestUtil {
eventually(() -> assertWithMessage("IME should be hidden").that(
callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
}
+
+ /** Verify IME is always hidden within the given time duration. */
+ public static void verifyImeIsAlwaysHidden(View view) {
+ always(
+ () ->
+ assertWithMessage("IME should be hidden")
+ .that(callOnMainSync(() -> isImeShown(view)))
+ .isFalse(),
+ VERIFY_DURATION);
+ }
+
+ /**
+ * Make sure that a {@link Runnable} always finishes without throwing a {@link Exception} in the
+ * given duration
+ *
+ * @param r The {@link Runnable} to run.
+ * @param timeoutMillis The number of milliseconds to wait for {@code r} to not throw
+ */
+ public static void always(ThrowingRunnable r, long timeoutMillis) {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ if (System.currentTimeMillis() - start >= timeoutMillis) {
+ return;
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ // Do nothing
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}